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.
Accessing Values
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.
The 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.
.creator
, .episodes
, and .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.
What about .name
though? Both records have a field called name
.
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.
Sorting Records
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:
Remember the 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.
Mapping Records
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 14
.
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.