Difference between type and type alias in Elm

Neeraj Singh

By Neeraj Singh

on July 12, 2017

What is the difference between type and type alias.

Elm FAQ has an answer to this question. However I could not fully understand the answer.

This is my attempt in explaining it.

What is type

In Elm everything has a type. Fire up elm-repl and you will see 4 is a number and "hello" is a String.

1> 4
24 : number
4> "hello"
5"hello" : String

Let's assume that we are working with users records and we have following attributes of those users.

  • Name
  • Age
  • Status (Active or Inactive)

It's pretty clear that "Name" should be of type "String" and "Age" should be of type "number".

Let's think about a moment what is the type of "Status". What is "Active" and "Inactive" in terms of type.

Active and Inactive are two valid values of Status. In other programming languages we might represent Status as an enum.

In Elm we need to create a new type. And that can be done as shown here.

1type Status = Active | Inactive

Second thing we are doing is that we are stating that the valid values for this new type are Active and Inactive.

When I discussed this code with my team members they asked me to show where is Active and Inactive defined. Good question.

The simple answer is that they are not defined anywhere. They do not need to be defined. What needs definition is the new type that is being created.

What makes understanding it a bit hard for people coming from Ruby, Java and such background is that these people (including me) are looking at Active and Inactive as a class or a constant which is not the right way to look at.

Active and Inactive are the valid values for type Status.

1> Active
2-- NAMING ERROR ----------
4Cannot find variable `Active`
63|   Active
7     ^^^^^^

As you can see repl is not sure what Active is.

We can solve this by pasting following code in repl.

1type Status = Active | Inactive

Now we can run the same code again. This time no error.

1> Active
2Active : Repl.Status

What is type alias

Let's see a simple application which just prints name and age of a single user.

Here is the code. I'm posting screenshot of the same below with certain part highlighted.

code without type alias

As you can see { name : String, age : Int } is repeated at four different places. In a bigger application it might get repeated more often.

This is what type alias does. It removes repetition. It removes verbosity.

As the name suggests this is just an alias. Note that type creates a new type whereas type alias is literally saving keystrokes. type alias does not create a new type.

Now if you read the FAQ answer again then hopefully it will make morse sense now.

Here is modified code using type alias.

Why use type alias Username : String

While browsing Elm code in general, I came across following code.

1type alias Username = String

Question is what does code like this buy us. All it does is that instead of String I can now type Username.

First let's see how it might be used.

Let's assume that we have a function which returns Status of a user for the given username.

The function might have implementation as shown below.

1getUserStatus username =
2  make_db_call_and_return_user_status

Now let's think about what the type annotation (rubyist think of it as method signature ) of function getUserStatus might look like.

It takes username as input and returns user record.

So the type annotation might look like

1getUserStatus : String -> Status

This works. However the issue is that String is not expressive enough. It can be made more expressive if the signature were

1getUserStatus : Username -> Status

Now that we know about type alias all we need to do is

1type alias Username = String

This makes code more expressive.

No recursion with type alias

An example of where we might need recursion is while designing commenting system. A comment can have sub-comments. However since type alias is just a substitution and recursion does not work with it.

1> type alias Comment = { message : String, responses : List Comment }
3This type alias is recursive, forming an infinite type!
52| type alias Comment = { message : String, responses : List Comment }
6   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7When I expand a recursive type alias, it just keeps getting bigger and bigger.
8So dealiasing results in an infinitely large type! Try this instead:
10    type Comment
11        = Comment { message : String, responses : List Comment }
13This is kind of a subtle distinction. I suggested the naive fix, but you can
14often do something a bit nicer. So I would recommend reading more at:

Hint for Recursive Type Aliases discusses this issue in greater detail and it also has solution to the problem of recursion.

Dual role of type alias as constructor and type

Let's say that we have following code.

1type alias UserInfo =
2    { name : String, age : Int }

Now we can use UserInfo as a constructor to create records.

1> type alias UserInfo = { name : String, age : Int }
2> sam = UserInfo "Sam" 24
3{ name = "Sam", age = 24 } : Repl.UserInfo

In the above case we used UserInfo as a constructor to create new user records. We did not use UserInfo as a type.

Now let's see another function.

1type alias UserInfo =
2    { name : String, age : Int }
5getUserAge : UserInfo -> Int
6getUserAge userinfo =
7    userinfo.age

In this case UserInfo is being used in type annotation as type and not as constructor.

Which one to use type or type alias

Both of them serve different purpose. Let's see an example.

Let's say that we have following code.

1type alias UserInfo =
2    { name : String, age : Int }
4type alias Coach =
5    { name : String, age : Int, sports : String }

Now let's write a function that gets age of the given userinfo.

1getUserAge : UserInfo -> Int
3getUserAge UserInfo =
4    UserInfo.age

Now let's create two types of users.

1sam = UserInfo "Sam" 24
2charlie = Coach "Charlie" 52 "Basketball"

Now let's try to get age of both of these people.

1getUserAge sam
2getUserAge charlie

Here is the complete version if you want to run it.

Please note that elm-repl does not support type annotation so you can't test this code in elm-repl.

The main point here is that since we used type alias, function getUserAge works for both UserInfo as well as Coach. It would be a stretch to say that this sounds like "duck typing in Elm" but it comes pretty close.

Yes Elm is statically typed language and it enforces type. However the point here is the type alias is not exactly a type.

So why did this code work.

It worked because of Elm's support for pattern matching for records.

As mentioned earlier type alias is just a shortcut for typing the verbose version. So let's expand the type annotation of getUserAge.

If we were not using type alias UserInfo then it might have looked like as shown below.

1getUserAge : { name : String, age : Int } -> Int

Here the argument is a record. Here is official guide on Records. While dealing with records Elm looks at the argument and if that argument is a record and has all the matching attributes then Elm will not complain because of its support for pattern matching.

Since Coach has both name and age attribute getUserAge charlie works.

You can test it by removing the attribute age from Coach and then you will see that Compiler will complain.

In summary if we want strict type enforcement then we should go for type. If we need something so that we do not need to type all the attributes all the time and we want pattern matching then we should go for type alias.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.