Elm – Mapping Anonymous Functions

In my prior post I wrote about creating a recursive function in Elm to iterate through a list of records and update a single record that met the search criteria.

I’ll admit that I was quite pleased with my solution not only because I had wanted to figure it out before looking at the instructor’s answer, but my implementation seemed particularly Elm-like.

So when I looked at the instructor’s solution, wondering how he would implement, I was pleasantly shocked (and a bit humbled) to see an entirely different way to implement iterate through a list an modify a particular record that didn’t use recursion.

Before I show you the instructor’s code I’ll need to talk about two things–how to create anonymous functions in Elm and Elm’s map function.

An anonymous function is a function that isn’t assigned to a particular identifier. They’re usually defined in-line. They’re parenthesized and the content is prefixed with a backslash. Anonymous functions are often used as arguments passed to higher-order functions or as part of a result of a higher-order function that needs to return a function.

The syntax for an anonymous function is:

(\param1 param2 ... -> -- Body)

(\x y -> x * y)
-- 6 : number

List.map (\n -> sayHello n) ["Alice", "Bob"]
-- ["Hello Alice", "Hello Bob"] : List String

By themselves anonymous functions are not much. But when you combine them with other functions they become much more powerful.

The map function can be used to apply a function to every element of a list or an array.

map : (a -> b) -> List a -> List b

map sqrt [1, 4, 9] == [1, 2, 3]
map not [True, False, True] == [False, True, False]

So map appears to have list and array iteration built-in. But there’s no standard Elm function that can examine the elements of a record, compare one element to the criteria, and modify another element based on the results of that evaluation. But with anonymous functions we can create a function to do that.

From the last example you see that it’s possible to create an anonymous function that map can use to modify a list of items. So let’s look at the instructor’s solution.

First, here’s the type alias of the model he’s passing in to his edit function. The model contains a list of players, the playerId of the player whose name we want to change, and the new name.

type alias Model =
    { players : List Player
    , name : String
    , playerId : Maybe Int
    }

type alias Player =
    { id : Int
    , name : String
    , points : Int
    }

Here’s his edit function.

edit : Model -> Int -> Model
edit model id =
    let 
        newPlayers =
            List.map
                (\player ->;
                    if player.id == id then
                        { player | name = model.name }
                    else
                        player
                )
                model.players
    in
        { model 
            | players = newPlayers
            , name = ""
            , playerId = Nothing
        }

The edit function takes a Model and an Int as parameters and returns a new Model (I like this much better than my way of passing in a Player record and a List of Player. It seems much cleaner since the Model contains everything you need).

The let/in construct allows you to create local variables in the let section which you can use in expressions in the in section.

A newPlayers list is created in the let section using map with an anonymous function. Each player record is passed into the anonymous function as a parameter. If the player.id matches edit’s id parameter then the player’s name is changed to the new name stored in model.name. If not, the unchanged player record is returned by the anonymous function.

A new model is created in the in section with the following changes and returned.

  • The old players list is replaced with newPlayers
  • name is set to an empty string
  • playerId is set to Nothing

I have to admit that I was both pleased and humbled to see this solution.

I’m pleased because it seemed so much more elegant and Elm-like than my recursive function (which I thought and still think is pretty cool). Also, the way map and anonymous functions are applied here could be used if my model contained other instances of player name in other lists that needed to be updated at the same time.

I’m humbled because looking at the instructor’s solution made me realize how much I don’t know about Elm and functional programming. When learning a new programming language, especially something as radical as Elm, I think I tend to approach it with the constructs and mindset of the old languages I know. It takes a while for me to “unlearn” what I know in order to embrace the new paradigm correctly (I hope).

I’m comforted by the fact that I’m just at the start of the journey, excited about what I’ll see along the way and wondering where I’ll end up. I’m looking forward to finding out what I don’t know.

This entry was posted in Software Development and tagged , , . Bookmark the permalink.