danielnarey/elm-toolkit/3.3.0

NOTE: this package is not compatible with Elm 0.19.1

Toolkit.Operators

A small set of custom infix operators for maintaining a consistent, unidirectional coding style when working with lists, Maybe and Result values, and functions that take multiple arguments

At 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.

Appending Things

(|++) : appendable -> appendable -> appendable

Append the RHS to the end of the LHS; equivalent to ++, but left-associative with precedence set to 0 (same as |>)

("ba" |> String.reverse) ++ "c"       --> "abc"
"ba" |> String.reverse ++ "c"         --> ERROR
"ba" |> String.reverse |++ "c"        --> "abc"
(|::) : List a -> a -> List a

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]
(:+:) : a -> a -> List a

Construct a list from the LHS and RHS; precedence is 5 and associativity is right (same as ++)

1 :+: 2    --> [1, 2]

Function Application

(||>) : a -> (a -> b) -> b

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"

with Lists

(.|>) : List a -> (a -> b) -> List b

Forward operator for List.map

[1,4,9] .|> sqrt    --> [1,2,3]
(:|>) : a -> (List a -> b) -> b

Wrap LHS in a list, then apply RHS function

1 :|> List.head   --> Just 1

with Tuples

(..|>) : ( a, a ) -> (a -> b) -> ( b, b )

Forward operator for map2Tuple

(1,2) ..|> (+) 1    --> (2,3)
(...|>) : ( a, a, a ) -> (a -> b) -> ( b, b, b )

Forward operator for map3Tuple

(1,2,3) ...|> (+) 1    --> (2,3,4)
(@@|>) : ( a, b ) -> (a -> b -> c) -> c

Forward operator for uncurry with 2 parameters

(1,2) @@|> (+)    --> 3
(@@@|>) : ( a, b, c ) -> (a -> b -> c -> d) -> d

Forward operator for uncurry with 3 parameters

(10, 20, 30) @@@|> clamp    --> 20

Error Handling with Maybe and Result Values

(?=) : Maybe a -> a -> a

Forward operator for Maybe.withDefault

Just 42 ?= 100    --> 42
Nothing ?= 100    --> 100
(!=) : Result x a -> a -> a

Forward operator for Result.withDefault

String.toInt "123" != 0    --> 123
String.toInt "abc" != 0    --> 0
(?|>) : Maybe a -> (a -> b) -> Maybe b

Forward operator for Maybe.map

Just 9 ?|> sqrt     --> Just 3
Nothing ?|> sqrt    --> Nothing
(!|>) : Result x a -> (a -> value) -> Result x value

Forward operator for Result.map

Ok 4.0 !|> sqrt             --> Ok 2.0
Err "bad input" !|> sqrt    --> Err "bad input"
(?+>) : Maybe a -> (a -> Maybe b) -> Maybe b

Forward operator for Maybe.andThen

List.head [1] ?+> always Nothing    --> Nothing
(!+>) : Result x a -> (a -> Result x b) -> Result x b

Forward operator for Result.andThen

String.toInt "1" !+> always (Err "ERROR")    --> Err "ERROR"