Let’s play with List.map in Elm. As a reminder, List.map’s annotation is

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

TL;DR

foo bar baz in Elm is not foo(bar, baz) in JS, it is foo(bar)(baz).

apply!

Basic Example

Here, we take our Model, a list of strings, and use List.map to convert it into a list of li nodes.

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 for Html.div

                              |  this part  |
div : List (Attribute msg) -> List (Html msg) -> Html msg

that works perfectly as our 2nd arg. All that this makes sense (eventually).

With the Index

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

  1. function that takes an int and something and returns something
  2. 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 Int to renderItem that gets inserted automatically.

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

“execute the "Gruffalo: " function and pass it renderItem

That’s all wrong.

Helper Function

We need a function that gets something and returns something according to the List.map annotation. (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, Elm runs 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.

https://guide.elm-lang.org/appendix/function_types.html

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.

Is there a better way to write this?

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.

Inline the Sucker

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 String that returns Html msg

The problem we ran into is that List.map wanted only two args:

  1. A function that takes in something and returns something (a -> b)
  2. A list of something List a

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

renderItem("Gruffalo: ")("red")

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.