Like tuples, records can also hold values of different types, but they are much more flexible than tuples.
Creating a Record
Records are created with curly brackets and their elements are separated by commas.
Unlike all data structures we have seen so far, records allow us to give names to the value contained in each element.
elm repl rearranges the position of each field in alphabetical order. Let’s assign the record above to a constant and while we’re at it create one more.
We created two records each containing information about a popular TV show. Let’s put them in a list.
Here’s the output after some formatting to make it look nicer:
Creating records with the literal syntax (i.e. curly brackets) can be tedious. Is there a better way? Let’s find out. First, let’s give the structure that underlies both records we just created a name.
type alias gives a name to an existing type. Using it we have given the underlying structure a name called
TVShow. Now we can create records much more cleanly.
With the new syntax, records are created by typing
TVShow followed by creator, name, and number of episodes. This syntax looks familiar, doesn’t it? It’s basically a function application.
type alias created a function for constructing records in addition to giving the underlying structure a name.
It’s important to notice that when defining a record structure using
type alias, we must use
: and not
= to separate the fields from their types.
Records are values like everything else in Elm. We can pass them to a function as an argument, modify them, and return them back. Let’s create a function that checks to see if a TV show record has a creator or not.
hasCreator function accesses the
creator field’s value using the dot syntax. Here are some more examples:
Special Accessor Functions
The second way to access elements in a record is with a special function.
.name are all special functions created behind the scenes when a record is created. These functions aren’t created by
type alias though. They are available to us even if we use the literal syntax instead of a constructor function to create a record.
Why do we call them special functions? First, they start with a dot. Normal functions aren’t allowed to start with special characters. Second, they can only be used on a record that actually has a field that matches the name of the special function. Let’s try using them to access a field in a record that doesn’t have the same name.
.name though? Both records have a field called
That works because
.name function isn’t tied to any of the records we created before
sapiens. All it cares about is whether a record has a field called
name or not. These special functions are equivalent to:
They take a record and return the value for the field represented by their name.
What do we gain by having special functions for accessing values in a record? After all the regular dot syntax works just fine and even looks more natural. To understand their usefulness, let’s try to sort TV shows by the number of episodes they have.
Here’s how the output looks after some formatting:
sortWith function from the
List module? We used it to sort a list of numbers in descending order like this:
sortWith accepts two arguments: a comparison function and a list that needs sorting.
sortByEpisodes is our comparison function that takes two TV shows and simply compares the number of episodes in them. As it turns out the
List module provides another function called
sortBy that makes sorting records much easier.
Here’s how the output looks after some formatting:
Instead of having to create a separate comparison function, we just tell
sortBy which field to use while sorting records.
sortBy then compares the specified field values and sorts a list accordingly. The first argument to
sortBy must still be a function though. Since
.episodes is a function, it’s perfectly fine to pass it to
sortBy. Here’s another example of
sortBy that sorts a list of strings by their length.
Earlier in the List section, we saw how to use the
map function to transform a given list by applying a function to every element in that list. Since a list can also contain records in it, we can use
map to transform those records into anything we like.
We took a list of records and transformed it to a list of strings by applying an anonymous function to each element. The anonymous function simply returns the value in the
name field. We can actually replace the anonymous function with the special function for accessing fields.
As you can see the special functions are quite useful when we want to pick just one field from each record and put them in a separate list.
Modifying a Record
Unlike tuples, we can modify values in a record. Remember all values in Elm are immutable and so are records. Because of that, Elm doesn’t really modify an existing record. It always returns a new one that contains the modified value. To understand how to modify a record, let’s create a function that increments the number of episodes by one.
As we can see, the number of episodes has been incremented to
15. But if we print the episodes in
firefly, it’s still
Elm didn’t change the record
firefly points to. It returned a new one. The syntax for modifying a record looks a bit weird, doesn’t it? Let’s deconstruct it.
We can also modify multiple fields at the same time.
The expressions for updating each field must be separated by commas. If the expression becomes too long to fit in one line, it’s perfectly fine to break it into multiple lines.
Note: Unlike all other data structures we’ve explored thus far, Elm doesn’t provide a separate module for records. That’s because other than the special accessors there are no functions available to manipulate records. Although that sounds limiting, a lot can be achieved just by using the syntax for modifying records. Furthermore, the special accessor functions are generated on the fly when a record is created. Therefore, we don’t need a separate module to house them.