In the previous part of the series, we implemented trains moving from one piece of track to the next. Our layout was still very constrained in that it only consisted of a series of straight tracks, and even though we used a graph to represent the layout internally, we did not yet utilize that power to implement branch lines. We will start with curves.

First, we need to put on our mathematician’s hat again. What does it mean to have a straight track or a curved track? For a straight track we already know part of the answer: Essentially, it has a length and nothing else. A curved track will have different specifications: The radius of the curve and the angle it covers. Its actual length can then be calculated using a function.

Side note: Real railway curves are much more complicated than circle segments. Among other things, they ease into their final radius gradually so the passengers heads don’t jerk due to the sudden centrifugal force when the train enters the curve. We will ignore this for now as it only makes the involved functions more complex without adding many additional points from as a programmer.

Elm provides union types to accommodate different variants of a data structures. They look like this:

type Track
    = StraightTrack
        { length : Float -- in m
        }
    | CurvedTrack
        { radius : Float -- in m
        , angle : Float -- in degrees, why not
        }

Note that this time, Track is not a type alias anymore but a proper type. Type aliases work just like find and replace in your favorite text editor: We could have written the whole data structure in the curly braces everytime instead of the alias. A type is however its own thing, and here we tell Elm that it can have two wholly different shapes. The names StraightTrack and CurvedTrack serve as tags to identify which kind of data structure to expect.

To see how this works, have a look at the brand new trackLength function that calculates the value differently based on the type of track.

trackLength : Track -> Float
trackLength track =
    case track of
        StraightTrack s ->
            s.length

        CurvedTrack c ->
            pi * c.radius * c.angle / 180.0

The case ... of syntax catches different variants of the Track type and the variable s or c is assigned the data structure within the specific type. From there, it is easy to handle the two cases and return the correct number. We have seen the case ... of syntax already when we use Maybe. Indeed, Maybe is defined as follows: type Maybe a = Nothing | Just a, where a is a type variable that can be replaced with any type of our choosing.

Next

In the next part we will finally implement branches in train tracks.