Maybe and Result values, and functions that take multiple argumentsAt some point after I started working in Elm, it became clear to me that debugging, refactoring, and extending existing code would be a lot easier if I conventionalized some stylistic choices so that code blocks would always be formatted in a consistent way. The built-in functional operators in Elm allow for a lot of flexibility in how code is written, but some of that flexibility has to be reined in if we want to emphasize readability as a virtue in functional programming.
In constructing some stylistic rules for my own code, the principle I decided to prioritize is what I call unidirectionality: the idea that a programmer should be able to readily discern the sequence of function calls in a code block by visually scanning the code from top to bottom, and then from left to right.
According to this principle, the "data" — that is, the value or set of values
that the function is called on — should always appear at the top of the code
block, with functions called on the data appearing on subsequent lines. A
nested series of function calls can appear on one line, but more complex code
chunks should be broken up into self-contained functions using let..in
statements. Nested list brackets and complex code chunks within list brackets
should generally be avoided.
With the above principle, the |> operator is used very liberally, the >>
operator is used only in rare cases, and there is no use case for "reverse"
(right-to-left) functional operators. In addition to liberal use of the |>
operator, I have found use cases for a small set of custom operators that help
to maintain consistent visual formatting and enhance readability of code blocks
by reducing clutter. I have included them here in one module so that I can
easily import them into other projects.
Append the item on the RHS to the end of the list on the LHS
[1] |:: 2 --> [1,2]
[1] |:: 2 |:: 3 --> [1,2,3]Construct a list from the LHS and RHS; precedence is 5 and associativity is
right (same as ++)
1 :+: 2 --> [1, 2]Forward function application with precedence set to 9. Allows you to avoid parentheses when you want the argument to appear before the function name in an inline expression.
1 ||> toString ++ 2 ||> toString --> "12"Forward operator for List.map
[1,4,9] .|> sqrt --> [1,2,3]Wrap LHS in a list, then apply RHS function
1 :|> List.head --> Just 1Maybe and Result ValuesForward operator for Maybe.withDefault
Just 42 ?= 100 --> 42
Nothing ?= 100 --> 100Forward operator for Result.withDefault
String.toInt "123" != 0 --> 123
String.toInt "abc" != 0 --> 0Forward operator for Maybe.map
Just 9 ?|> sqrt --> Just 3
Nothing ?|> sqrt --> NothingForward operator for Result.map
Ok 4.0 !|> sqrt --> Ok 2.0
Err "bad input" !|> sqrt --> Err "bad input"
Append the RHS to the end of the LHS; equivalent to
++, but left-associative with precedence set to0(same as|>)