The goal of the style guide is foremost to promote consistency and reuse of pattern from other languages in order to improve readability and make Elm easier for beginners. This includes moving Elm away from Haskell’s indentation style and even making some parts look closer to JavaScript. These decisions are intentional.
We would like Elm to look friendly and familiar to users of any language — especially JavaScript — so they can discover Elm’s powerful features without being overwhelmed. This does not intend to weaken or discourage any features of Elm, but instead to make them more accessible.
A secondary goal of the style guide is to encourage short diffs when changes are made. This makes changes more clear, and helps when multiple people are collaborating.
Elm code should use 4-space indents everywhere. This differs from other functional programming languages such as Haskell and OCaml, which use variable number of spaces depending on the syntactic construct (ie. let, if). Consistent 4-space indentation will make Elm code:
- easier to use in editors without Elm support,
- easier to follow visually,
- less surprising for users of JavaScript.
It is important to use only spaces, not tabs. Usually the tabs vs. spaces debate is abstract and subjective. But in Elm, spaces are required to match the indentation level of the let
keyboard when there are multiple variables:
calc box =
let x = box.position.x
y = box.position.y
in
x * y
If tabs are used and the file is viewed on GitHub where the tab width is different, then y = ...
will not line up with x = ...
on the line above.
- There should be two empty lines between top-level definitions.
- There should be at most one empty line inside a definition.
In some cases, the remainder of a line should be left empty to improve readability. The partially empty line helps to indicate the beginning or end of a record, list or function definition by visually dividing it from the rest of the code.
- After
=
or->
when the right-hand side has multiple lines:
let width =
Json.getProp "width" jsonValue
|> Json.toInt
|> Maybe.getDefault 0
- After
{
or[
when starting a list or record, and after}
or]
when closing one:
let urls = [
"http://elm-lang.org",
"http://example.com",
]
imageProps = {
url: "http://example.com/image.png",
width: 100,
height: 50,
}
To see why this is necessary, consider this example:
{- incorrect style -}
let propUrl = Json.getProp "url" jsonValue
|> Json.toString
|> Maybe.getDefault ""
The second and third lines cannot be properly indented with a multiple of 4 spaces. |> ...
can only be 2 or 6 spaces ahead of Json.getProp
or 2 spaces behind it.
More importantly, if we later decide to change the name of propUrl
we would have to re-indent all of following lines, creating a 3-line diff instead of a 1-line diff. As the code grows it becomes more tedious and confusing to re-indent all the lines.
{- incorrect style -}
-let propUrl = Json.getProp "url" jsonValue
- |> Json.toString
- |> Maybe.getDefault ""
+let jsonPropUrl = Json.getProp "url" jsonValue
+ |> Json.toString
+ |> Maybe.getDefault ""
calc box =
let first = box.position.x
second = box.position.y
in
first * second
- The first variable should be placed directly after
let
without a new line. - If there are multiple variables, all should be indented to match the start of the first variable.
in
should exist on its own line, and the expression should follow on a new line which is indented to match the variables above.
getName full =
if full then "Evan Czaplicki" else "Evan"
getName full =
if full
then "Evan Czaplicki"
else "Evan"
- It is recommended to use multi-way
if
when multiple lines are needed, butif ... then ... else
can be used on multiple lines in some cases (ie. here it is useful becauseEvan
andEvan
line up). - For the multiple line version
then
andelse
should be on their own lines and indented one forward from theif
.
keyboardAction key n =
if | key == 40 -> n+1
| key == 38 -> n-1
| otherwise -> n
if
should be followed by two spaces, a|
, another space and the first condition.- The
|
should always line up with one above it.
keyboardDesc key =
if | key == 40 ->
"You pressed the down arrow, which has key code " ++ show key
| key == 38 ->
"You pressed the up arrow, which has key code " ++ show key
| otherwise ->
"I don't know what you pressed"
- If the expression after the
->
is too long or has multiple lines, it should be moved onto a new line with another indent.
keyboardDesc key =
case key of
40 ->
"You pressed the down arrow, which has key code " ++ show key
38 ->
"You pressed the up arrow, which has key code " ++ show key
otherwise ->
"I don't know what you pressed"
- There should always be a new line with an indent after
case ... of
so that if the variable name changes the following lines don't have to be re-indented. - The expression after
->
should be on a new line if it is too long or consists of multiple lines (same as for multi-wayif
).
let urls = [
"http://elm-lang.org",
"http://example.com",
]
numberNames = [ "one", "two", "three", "four" ]
numbers = map show [
1,
2,
3,
4,
]
in
[
urls,
numbers,
]
- If the list fits on one line, there should be a space after the
[
, before the]
and after every,
. - If the list spans multiple lines, the line should be empty after
[
and]
should be on its own non-indented line. - The
[
can share a line with the=
(or with a function name in case of function application), but not with the first item in the list. - If the list spans multiple lines, each item should be on its own line with an indent and a comma after it. Even the last line should have a comma so that when you add another element to the list you get a one-line diff instead of two-line. NOTE Elm's parser (as of 0.13) does not allow a comma after the last item so it is not possible to use this style yet.
let urls = {
elm = "http://elm-lang.org",
example = "http://example.com",
}
numbers = { one = 1, two = 2, three = 3, four = 4 }
in
{ state |
urls <- urls,
numbers <- numbers,
}
- Same rules as lists.
- There should be spaces on either side of the
=
. - When updating a record, the
{ ... |
should stay together on the same line, and the property updates should be on new indented lines.
data Kilometer = Kilometer Float
data Color
= Red
| Blue
| Green
| Rgb Int Int Int
- If there is more than one constructor, all of them should be on a new line.
- The
=
should be on the left of the first constructor and line up with the|
below. This is the only exception where the=
does not stay with the left-hand side and does not have white-space after it.
scene config state results (w, h) =
toElement
config.width
h
(searchWidgetElement
config
(Labels.labels config.language)
state
results)
- If the list of arguments is too long, each argument should be put on its own line and indented once.
- This style can be nested, as long as the code remains clear.
on "keyup"
getValue
actions.handle
UpdateSearchText
- The exception to this rule is when the function name has 3 or less characters. In this case the first argument can be on the same line as the function and still line up with the left edge of the arguments below.
stringToResult str =
Json.parse str
`Maybe.andThen` Json.getProp "items"
`Maybe.andThen` Json.arrayMap itemJsonToEntry
|> Maybe.getDefault []
longString =
"The first part"
++ " continue on this line"
++ " and ends here"
widget config =
scene
<~ config
~ state
~ (searchResults config)
~ Window.dimensions
- If the expression is too long to fit on one line, each operator should be on a new indented line.
- Notice in
Json.parse str
that if operators are on a new line, the argumentstr
should not. The function application style with arguments on new lines should not be mixed with operators on new lines. If the expressions between the operators are too long, alet
variable should be used.
David Biro's image search widget has been converted to use this style guide in all Elm code.
imageJsonToEntry url imageJson = {
url = url,
width = getIntPropOrElse 0 "width" imageJson,
height = getIntPropOrElse 0 "height" imageJson,
thumbnailUrl = getStringPropOrElse "" "thumbnailLink" imageJson,
thumbnailWidth = getIntPropOrElse 0 "thumbnailWidth" imageJson,
thumbnailHeight = getIntPropOrElse 0 "thumbnailHeight" imageJson
}
- This is not a good idea, because if the record is extended to include another, longer field name all the lines will have to be re-aligned.