-
Notifications
You must be signed in to change notification settings - Fork 562
Compiler Style Guide
This is a style guide for the Haskell code that comprises the PureScript compiler. The recommended style guide for PureScript code is available here.
This page is a work in progress; compiler maintainers should currently feel free to debate and edit this document until it stops being contentious, at which point we may migrate it into the repo proper.
In rough order from most to least important—that is, you can read each point as if it were followed by ‘when this doesn't conflict with a previous point’.
- Code should be as readable as possible, assuming the reader is a proficient-but-not-legendary Haskell programmer.
- Code should minimize the number of edits needed to make changes.
- Code should be internally consistent—when practical, use the same approach to express similar ideas in nearby locations.
- Less code is better.
Try to format comments to wrap around 80 characters, and find ways to break up lines of code that are longer than 120-ish characters. If readability isn't served by this guideline, ignore it.
Generally: two spaces, no tabs. When indentation is used to align elements of a syntactic form that spans multiple lines, use the indentation in a way that doesn't require every line to change if the initial line changes (principle 2).
Show code
Choose one:
data Either a b = Left a | Right b
data Either a b
= Left a
| Right b
Do not write:
data Either a b = Left a
| Right b
Show code
Choose one:
foo :: forall a. ClassName a => Either Text a -> a -> a
foo
:: forall a
. ClassName a
=> Either Text a
-> a
-> a
Do not write:
foo :: forall a
. ClassName a
=> Either Text a
-> a
-> a
Show code
Choose one:
foo a b = bar a . baz $ qux b
foo a b
= bar a
. baz
$ qux b
Do not write:
foo a b = bar a
. baz
$ qux b
Use sparingly?
Do use a custom data type instead of a Bool
if at least one of the following applies:
- the type is used in an exported member
- a value of the type is used in a data structure which doesn't clarify the meaning of the value
- it is easy to imagine a third option being added
Do declare a data type as a record if it has only one constructor and at least one of the following applies:
- it is a simple newtype wrapper (prefix the accessor with one of the standard unwrapping words:
get
if the newtype is for instance selection (like using a particularMonoid
),run
if the newtype wraps a computation,un
otherwise) - it has three or more arguments (prefix the accessor with an abbreviation of the type name)
- it is expected to expand in the future (prefix the accessor with an abbreviation of the type name)
Consider do notation if:
- it reads best
- it is used in adjacent branches
Avoid do notation if none of the above apply and:
- no monadic work is being done
- there is a simple way to express the computation directly with operators
Use infix notation for functions with the following names:
-
elem
,notElem
,member
,notMember
-
cons
,snoc
on
-
difference
,intersect
,union
-
isPrefixOf
,isSuffixOf
, generally anything namedis*Of
Don't use infix notation for functions which would otherwise not merit it, just to avoid using flip
: flip f x
is equivalent to (`f` x)
, but the former is generally more readable. (???)
Avoid catch-all patterns unless your logic is truly agnostic to new constructors being added to the type being matched.
Example
Do write:-- | Extract the value of a Foo.
maybeFooValue = \case
Foo value -> Just value
_ -> Nothing
Do not write:
-- | Extract a wibble, which is something that may or may not be present in any ctor.
findWibble = \case
Foo wibble -> Just wibble
Bar _ wibble -> Just wibble
_ -> Nothing
When matching a data type, use the empty record pattern (CtorName{}
) instead of wildcards (CtorName _ _
) if the logic of the case would not change if new fields were added to that constructor.
Prefer lambda-case to multiple equational declarations, when possible. (This follows from principle 2; repeating the name of the function means more places to edit if that function is renamed.)