map : (a -> b) -> List a -> List b
It takes a function (that takes something and returns something) and a list of something and returns a list of something. (Sorry to be so technical.)
foo bar baz in Elm is not
foo(bar, baz) in JS, it is
Here, we take our
Model, a list of strings, and use List.map to convert it
into a list of
type alias Model = List String renderItem : String -> Html msg renderItem item = li  [ text item ] view : Model -> Html msg view model = ul  (List.map renderItem model)
renderItem takes something and returns something. It’s annotation is
renderItem : String -> Html msg
which could also be expressed as
(a -> b). That just happens to be the first
part of List.map’s annotation, so List.map can use it to transform our list
of strings (
List String) into a
List (Html msg). Looking at the annotation
| this part | div : List (Attribute msg) -> List (Html msg) -> Html msg
that works perfectly as our 2nd arg. All that this makes sense (eventually).
If we want to include the index, we can use List.indexedMap instead. It’s annotation is
indexedMap : (Int -> a -> b) -> List a -> List b
It takes a
- function that takes an int and something and returns something
- a list of something
and returns a list of something.
After a few minor changes, we’ve got
renderItem : Int -> String -> Html msg renderItem index item = li  [ text (String.fromInt index ++ ": " ++ item) ] view : Model -> Html msg view model = ul  (List.indexedMap renderItem model)
This is pretty simple. We’ve just added another
renderItem that gets
A Custom Prefix
But what if we’re in a different situation? Like List.indexedMap, we want to include another piece of data, but something aside from the index.
renderItem : String -> String -> Html msg renderItem prefix item = li  [ text (prefix ++ item) ]
That seems to make sense. However, when we try to call it, including the additional value…
view model = ul  (List.map "Gruffalo: " renderItem model)
We get an error.
The `map` function expects 2 arguments, but it got 3 instead. 42| ul  (List.map "Gruffalo: " renderItem model) ^^^^^^^^ Are there any missing commas? Or missing parentheses?
How should we handle this?
Let’s combine the two and see if that works. That way List.map is only getting two args again.
view model = ul  (List.map ("Gruffalo: " renderItem) model)
This value is not a function, but it was given 1 argument. 42| ul  (List.map ("Gruffalo: " renderItem) model) ^^^^^^^^^^^^ Are there any missing commas? Or missing parentheses?
Nope. This forces Elm to try and evaluate the (…) stuff, but it reads it as
"Gruffalo: "function and pass it
That’s all wrong.
We need a function that gets something and returns something according to the
(a -> b)
What if we write a function to return the function we want to use for
List.map? That seems pretty functional, right?
getItemRenderer : String -> (String -> Html msg) getItemRenderer prefix = renderItem prefix view : Model -> Html msg view model = ul  (List.map (getItemRenderer "Gruffalo: ") model)
Hey, it works! Now, when we see
List.map (getItemRenderer "Gruffalo: ") model,
getItemRenderer "Gruffalo: " first again, but this time, the first
value is a function. It, in turn, returns another function
(String -> Html
msg), which is fed into
List.map as the first arg.
How does it work?
So conceptually, every function accepts one argument. It may return another function that accepts one argument. Etc. At some point it will stop returning functions.
Everything can be curried in Elm! That allows us to call
renderItem with just
the prefix, before we know what values we want. Doing so preloads the first
value and returns a function that’s only needs one more value in order to glue
it all together.
view model = let itemRenderer = renderItem "Gruffalo: " in ul  (List.map itemRenderer model)
If we do it like this, it’s a bit less code. It means this trick can’t be used elsewhere, though. Does that really matter in this case? It’s a pretty lame little function, more annotation than anything else. It’s worth thinking about, though, if it’s a non-trivial bit of logic used in the helper function.
If we do inline it like this, though, it makes another option pretty obvious.
view model = ul  (List.map (renderItem "Gruffalo: ") model)
There’s the eureka moment. 🤦
If we have a function with arity of 2 - hold tight… We don’t. Stop thinking like that. When we see
renderItem : String -> String -> Html msg
we may think of this as an arity of 2, but it’s not. As stated before, this is
a function that accepts
Int and returns a function that accepts
The problem we ran into is that
List.map wanted only two args:
- A function that takes in something and returns something
(a -> b)
- A list of something
We couldn’t figure out how to use
renderItem with our prefix, because we
thought we had to call it with the prefix and the value at the same time!
Maybe it’s because of the notation.
renderItem "Gruffalo: " "red"
would be written like this in JS
not like this
renderItem("Gruffalo: ", "red")
That’s a pretty significant difference. Once we arrive at this conslusion, it seems obvious. Before that, though, it can be really confusing. I hope this helps. Once it clicks, it’s seems really nice.