Goal: a consistent style that is easy to read and produces clean diffs. This means trading aggressively compact code for regularity and ease of modification.
Line Length
Keep it under 80 characters. Going over is not the end of the world, but consider refactoring before you decide a line really must be longer. Long lines can be a good sign that it is time to break things up and refactor.
Variables
Be Descriptive. One character abbreviations are rarely acceptable, especially not as arguments for top-level function declarations where you have no real context about what they are.
Qualify variables. Always prefer qualified names. Set.union
is always
preferable to union
. In large files and in large projects, it becomes very
very difficult to figure out where variables came from without this.
Declarations
Always have type annotations on top-level definitions.
Always have 2 empty lines between top-level declarations.
Always bring the body of the declaration down one line.
Good
homeDirectory : String
homeDirectory =
"/root/files"
evaluate : Boolean -> Bool
evaluate boolean =
case boolean of
Literal bool ->
bool
Not b ->
not (evaluate b)
And b b_ ->
evaluate b && evaluate b_
Or b b_ ->
evaluate b || evaluate b_
Now imagine one of the cases in evaluate
becomes drastically more
complicated. Nothing needs to be reformatted so the diff will be minimal and
the result will still look quite nice.
Bad
homeDirectory = "/root/files"
eval boolean = case boolean of
Literal bool -> bool
Not b -> not (eval b)
And b b_ -> eval b && eval b_
Or b b_ -> eval b || eval b_
We saved vertical lines here, but at the cost of regularity and ease of
modification. If Literal
ever becomes longer, all arrows must move. If any
branch gets too long, everything needs to come down a line anyway.
Having case
appear later than the actual cases is strongly discouraged. It
should serve as a context clue that makes glancing through code easy, but when
indented in crazy ways, it becomes more difficult to glance through.
Types
Do not be a maniac with indentation. Simplicity will be better in the long run.
Good
type Boolean
= Literal Bool
| Not Boolean
| And Boolean Boolean
| Or Boolean Boolean
type alias Circle =
{ x : Float
, y : Float
, radius : Float
}
type alias Graph =
List (Int, List Int)
Bad
type Boolean = Literal Bool
| Not Boolean
| And Boolean Boolean
| Or Boolean Boolean
type alias Circle = {
x : Float,
y : Float,
radius : Float
}
type alias Graph = List (Int, List Int)
Changing the type name Boolean
will change the indentation on all subsequent
lines. This leads to messy diffs with indentation changes that provide no
meaningful value.
If we ever add a new field to Circle
that is longer than radius
, we have to
change the indentation of all lines, leading to a bad diff. Furthermore, ending
lines with a comma makes diffs messier because adding a field must change two
lines instead of one.
If we change the name of the type alias Graph
, the diff will be less clear if
everything is on the same line. Did we change the name or the meaning?
Furthermore, if the type being aliased ever becomes too long, it will need to
move down a line anyway.