-
Notifications
You must be signed in to change notification settings - Fork 569
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 bdata Either a b
= Left a
| Right bDo not write:
data Either a b = Left a
| Right bShow code
Choose one:
foo :: forall a. ClassName a => Either Text a -> a -> afoo
:: forall a
. ClassName a
=> Either Text a
-> a
-> aDo not write:
foo :: forall a
. ClassName a
=> Either Text a
-> a
-> aShow code
Choose one:
foo a b = bar a . baz $ qux bfoo a b
= bar a
. baz
$ qux bDo not write:
foo a b = bar a
. baz
$ qux bUse 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:
getif the newtype is for instance selection (like using a particularMonoid),runif the newtype wraps a computation,unotherwise) - 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
_ -> NothingDo 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
_ -> NothingWhen 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.)