Tracks
/
Elm
Elm
/
Syllabus
/
Records
Re

Records in Elm

11 exercises

About Records

Records are data structures grouping together related information with labels. They are similar to objects in JavaScript or Java, or structs in C or Rust, but with some key distinctions.

Creating records

Records are created with curly brackets and their elements are separated by commas.

firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

The type of a record is also defined with a similar syntax, except equal signs = are replaced by colon signs :.

firefly : { name : String, creator : String, episodes : Int }
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

Repeating these type definitions is cumbersome, so using a type alias to share the definition is a common idiom.

type alias TvSeries =
    { name : String
    , creator : String
    , episodes : Int
    }

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

The type alias also automatically supplies a constructor function for the record. This is sometimes surprising since functions in Elm must start with a lowercase character, but type constructors are one exception.

firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14

Accessing record fields

The main way to access a field value of a record instance is to use the dot-notation such as firefly.creator. The Elm compiler also supports special accessor functions starting with a dot . such as .creator that will work on any record with a field of that name. That is the second exception to the rule that functions must start with a lowercase character. In fact user-defined functions must start with a lowercase character, but the two examples we just discovered are generated by the compiler, not the programmer.

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

firefly.name
    --> "Firefly"

.creator firefly
    --> "Joss Whedon"

Records can also be destructured in bindings using the record pattern matching. Destructuring works with any record that has fields of the relevant names.

episodesCount : TvSeries -> Int
episodesCount { episodes } =
    episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
    let
        { name, creator } = show
    in
    { name = name
    , creator = creator
    , episodes = episodesCount show
    }

Updating records

Records are immutable, as everything in Elm, so once a record is defined, its field values can never change. There is a special syntax to create a copy of an existing record, but changing one or more fields. This syntax uses the pipe symbol | to distinguish the original record and the fields that will change, such as { oldRecord | field1 = newValue }.

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

updatedFirefly : TvSeries
updatedFirefly =
    { firefly | creator = "J Whedon", episodes = 15 }

Comparing records

Elm uses [structural equality][equality], which means that two instances of the same record with identical values are equal.

firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
    --> True

Extensible records

Elm also supports structural typing meaning that if a function requires a record with an x and y field, it will work with any record that has those fields such as 2D points, 3D points, spaceships, etc. Those record types have a pipe | in their definition, such as { a | x : Float, y : Float } and are called "extensible records" in Elm terminology. Beware of the difference between a pipe | in a type definition, which is an extensible record definition ({ a | x : Int }), and a pipe | an actual record instance which means that we are updating some fields of that record.

point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
    sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
    { vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }
Edit via GitHub The link opens in a new window or tab

Learn Records

Practicing is locked

Unlock 3 more exercises to practice Records