An intro to constructors in Elm

Some of your types come with Free Functions

Wouter In t Velt
Elm shorts
6 min readJan 18, 2017

--

Construction | Image credit : Haitham alfalah, via Wikimedia Commons

Consider a typical type definition of aMsg type in elm:

type Msg 
= UserInput String
| LoggedIn User
| Guest User
| ClickedLogoff

In this definition, the types are

  • Msg: this is the type that is being defined.
  • String: this is the (built-in) type we are using ‘inside’ the Msg type.
  • User: some custom type

What about UserInput, LoggedIn, Guest and ClickedLogoff?
They are functions called constructors. You get them for free. If you define the type, you get the constructors as well.

Constructor functions

UserInput is a function with a capitalized name. It has the signature String -> Msg. Which means it takes a String and outputs a Msg.

And it’s called a constructor. Because it constructs a Msg type. In
something = UserInput value, the UserInput value part constructs a Msg. So the variable something will be of the type Msg and will hold the resulting value of UserInput value.

LoggedIn is also a function, which takes a User as its argument. It outputs a Msg too. And Guest is a function too, with the exact same argument User, and the same signature.

ClickedLogoff is also a constructor, of an equally useful nature (albeit somewhat boring). It takes no arguments, and simply returns a Msg.

You typically call a function like UserInput in your view. You pass it to the onInput handler, when you render the input field:

input [ type_ "text", onInput UserInput ] ...

The onInput handler is from the Html.Events library. It has the following type signature.

onInput : (String -> msg) -> Attribute msg

This function takes one argument, a function. It wants you to provide a function that turns a String into a msg. The msg in the signature is in lowercase. The onInput function does not care what type the msg is. That is up to you. It will simply include that type in the output of the function, in the Attribute msg. (Attribute msg is the type for attributes of HTML elements. In the Html.Attribute package there is some explanation and examples)

The UserInput constructor defined earlier matches this signature and does exactly what’s needed here: it takes a String (which is whatever the user typed in the input field), and turns it into a Msg.

Constructors in pattern matching

You can also use these constructors to access the contents of a variable later on. Using a case .. of statement we can

  1. Find out which constructor was used.
  2. Access the contents “behind” that constructor (if there is any).

In the example, both the LoggedIn constructor and the Guest constructor carry a User. With a case statement, we can find out which constructor was used, and access the user information:

case msg of
LoggedIn normalUser ->
.. handle the info from the variable normalUser...

Guest guestUser ->
.. handle info from guestUser ..

Records come with constructors too!

Consider this record definition in Elm:

type alias User =
{ name : String
, age : Int
}

By naming this record User, you also get a constructor for free. The constructor is also called User, and it has the signature (in this example) String -> Int -> User. This function allows you to do User "Bill" 42 to create a new user record.

At first, it may seem weird — maybe even conflicting — that the type and its constructor have the same name. But they will never show up in the same place in code:

  • In type definitions of other types, it is always the type, never the constructor. In the Msg type definition, the User that shows up (twice) is the type.
  • When User shows up in code in type signatures like
    setName : String -> User -> User, it is always the type, never the constructor.
  • In all other places in your code, if you encounter a User, it will be the constructor. So for example, in the line newUser = User “” 0, the User there is the constructor.

Type alias or type?

When you make a type for the user record from the example above, you can actually do this in two ways:

-- Option 1:
type alias User =
{ name : String
, age : Int
}

-- Option 2 (called "opaque", or "strong typed")
type User =
User
{ name : String
, age : Int
}

In option 2 you make the constructor function explicit.
I could have given the constructor any name, but if you have only 1 constructor, it is common to give it the same name as the type.

The compiler is much more forgiving on type aliases. You are basically saying that User is just an alias for anything that is a record with a String called name and an Int called age. So if you have some function defined as:

hasLegalAge : User -> Bool
hasLegalAge user =
...

It is fine if you call it like this:

canBuyOurStuff =
hasLegalAge { name = "", age = 28 }

With a strong typed User, the compiler will not let you do that. It will complain that you passed some record to hasLegalAge, but it expected a real (strong) User type.

The real User type can be made by calling the constructor function, which is also called User, so if you change the code to this, it will compile again:

canBuyOurStuff =
hasLegalAge (User { name = "", age = 28 })

The second strong typed code looks much more complicated, so what’s the use of strong types?

For one, it makes it easier to extend the User type. If at some point you want to deal with anonymous users, you can add another constructor to your type:

type User
= User
{ name : String
, age : Int
}
| Anonymous

And the canBuyOurStuff function from above will still work (!). So less refactoring if you extend your strong type with more constructors.

Another advantage to strong types: If you are making a library, you can make more changes without breaking other code. This happens if you do not make the User constructor available to the rest of the world. If your module definition is like this

module User exposing (User, newUser, setName, ...)

You would expose the User type, and some custom (lowercase) functions to create or modify the User. This gives you the freedom to make changes to the inner workings of the User type, without breaking the existing functions you exposed. You could add fields for e.g address, hobbies, pets, friends etcetera to the record (and expose more functions to read and write them). And you can do this without breaking the original functions, so that anyone or any code that imports the original functions can still use them without refactoring.

Further reading

If you want to know more about types (or if my post was still not clear), here are some other good resources:

The first time I got really confused about types and constructors with the same name, was when I got started with Json decoding. Which is a mind-bender in itself. The example with the function map2 caused a short-circuit in my brain: “Whut? What is this type doing in a function?” It took me a while to figure out that this was not the “type” that was being referenced, but the constructor for the type alias I defined. [AHA moment]
Hopefully this post will save you from a similar short-circuit. Happy coding!

--

--

Elm shorts
Elm shorts

Published in Elm shorts

Random examples, patterns, ideas, tutorials and other stuff. Mostly short posts, sometimes longer. Things I learned in playing with the beautiful language Elm, with help from its awesome community. Hopefully these posts will help you in learning Elm and improving your skills.

Wouter In t Velt
Wouter In t Velt

Written by Wouter In t Velt

Corporate Digital Transformation Manager by day, Developing in Elm by night. Fascinated by UX, FP and readable code.

Responses (4)