Considering my last post in this series didn't attract a lot of attention, I've decided to keep it a bit short. If you're interested in writing invoices in Elm, I've made a public repository. I'm using it myself, but at the time of writing it certainly still requires some polishing up.
So let's instead focus on what everybody seems to care about the most, functors and monads.
type ColumnSize = [...] columnSizeToString : ColumnSize -> String columnSizeToString gs = [...] columns : List (Html msg, ColumnSize) -> Html msg columns cs = let columnSizes : String columnSizes = List.map ( columnSizeToString << Tuple.second ) cs |> List.intersperse " " |> String.concat inDiv : Html msg -> Html msg inDiv element = div  [element] in div [ style "display" "grid" , style "grid-template-columns" columnSizes ] ( List.map ( inDiv << Tuple.first ) cs )
What's going on here?
columns puts multiple elements into columns, using
I decided I wanted to have every column dictate its own width. So rather than passing a list of
Html msg, we pass a list of
(Html msg, ColumnSize). By coupling elements with their widths into tuples, we ensure that we have exactly as many widths as columns.
ColumnSize is a type I have declared previously denoting how wide the column should be (e.g.
50px). I'm using
ColumnSize rather than
String so the correctness of the argument is checked at compile time. If I'd used
String, I could have made a column that is
"7 kg" long, which would result in a silent error at runtime rather than a loud and obnoxious one at compile time.
Now let's look at
columnSizes. It constructs a string denoting the grid's column template, using the list of tuples given as input.
This is where functors come into play. As we've seen previously, a functor is any unary type constructor which has a map function. This is the case for lists: a lists by itself is not a type, but e.g. a list of strings is, and it has a map function that is applies a function to all elements of the list, returning a new list.
That is exactly what happens here:
List.map ( columnSizeToString << Tuple.second ) cs
we create a function by composing
Tuple.second, this function is passed to
List.map, and the resulting function is applied over cs, the list of tuples that was input of the parent function.
So, for each element of cs (a tuple
(Html msg, ColumnSize)), we first apply
Tuple.second, the first part of the composition. This returns the second element of the tuple (a
ColumnSize). We feed that to the second element of the composition,
columnSizeToString, which returns a string. The result of the whole expression is therefore a
In the next line:
|> List.intersperse " "
we pipe the result of the previous expression (out list of strings) into
List.intersperse " ", which intersperses space elements into our list.
we pipe into
String.concat, which turns a list of strings into a single string.
Now, let's look at some code were we use the monadic properties of a list. This is a snippet from inside the
entryLayout : List (String, String) -> Html msg entryLayout entry = case entry of  -> div   (headerKey, headerValue) :: entryRemainder -> entryHeaderLayout headerKey headerValue :: List.map entryBodyLayout entryRemainder |> List.map (\(a, b) -> [a, b]) |> List.concat |> div [ style "display" "grid" , style "grid-template-columns" "2fr 5fr" , style "grid-column-gap" "10px" ] entryHeaderLayout : String -> String -> (Html msg, Html msg) entryHeaderLayout k v = [...] entryBodyLayout : ( String, String ) -> (Html msg, Html msg) entryBodyLayout (k, v) = [...]
This code turns pairs of strings into a grid element with 2 neatly aligned columns.
The following lines:
entryHeaderLayout headerKey headerValue :: List.map entryBodyLayout entryRemainder
create a list with type
List (Html msg, Html msg). They are not particularly interesting so I won't delve into them further.
Rather than a list of tuples, we need a list that looks like
[key1, value1, key2, value2,...], as that is what a grid element needs as children. We can turn a tuple into a list using
\(a, b) -> [a, b], and we could use the
map function, but that would leave us with a list of lists of elements, which is not what we want.
Because a list is a monad, we know that we can 'bind' a function.
So a function of type
type1 -> List type2, which is just the type of
\(a, b) -> [a, b], can be 'bound' so that a
List type1 can be turned into a
List type2. Essentially, a flat map. This is what we want! In our case,
(Html msg, Html msg) and
List gives us monadic properties with its
concat function, which flattens a
List (List a) into
Because we already have
map, we can simply flatten after mapping, which is what we do here:
|> List.map (\(a, b) -> [a, b]) |> List.concat
That's it! All it takes to be a monad: a map and a flat map function.
Okay technically you also need a singleton
a -> F a function but that one is usually trivial.