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.
In Elm everything has a type. Fire up elm-repl
and you will see 4 is a
number
and "hello" is a String
.
> 4
4 : number
> "hello"
"hello" : String
Let's assume that we are working with users records and we have following attributes of those users.
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.
type 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
.
> Active
-- NAMING ERROR ----------
Cannot find variable `Active`
3| Active
^^^^^^
As you can see repl is not sure what Active
is.
We can solve this by pasting following code in repl.
type Status = Active | Inactive
Now we can run the same code again. This time no error.
> Active
Active : Repl.Status
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.
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
.
While browsing Elm code in general, I came across following code.
type 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.
getUserStatus username =
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
getUserStatus : String -> Status
This works. However the issue is that String
is not expressive enough. It can
be made more expressive if the signature were
getUserStatus : Username -> Status
Now that we know about type alias
all we need to do is
type alias Username = String
This makes code more expressive.
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.
> type alias Comment = { message : String, responses : List Comment }
This type alias is recursive, forming an infinite type!
2| type alias Comment = { message : String, responses : List Comment }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When I expand a recursive type alias, it just keeps getting bigger and bigger.
So dealiasing results in an infinitely large type! Try this instead:
type Comment
= Comment { message : String, responses : List Comment }
This is kind of a subtle distinction. I suggested the naive fix, but you can
often do something a bit nicer. So I would recommend reading more at:
<https://github.com/elm-lang/elm-compiler/blob/0.18.0/hints/recursive-alias.md>
Hint for Recursive Type Aliases discusses this issue in greater detail and it also has solution to the problem of recursion.
Let's say that we have following code.
type alias UserInfo =
{ name : String, age : Int }
Now we can use UserInfo
as a constructor to create records.
> type alias UserInfo = { name : String, age : Int }
> sam = UserInfo "Sam" 24
{ 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.
type alias UserInfo =
{ name : String, age : Int }
getUserAge : UserInfo -> Int
getUserAge userinfo =
userinfo.age
In this case UserInfo
is being used in type annotation as type and not
as constructor.
Both of them serve different purpose. Let's see an example.
Let's say that we have following code.
type alias UserInfo =
{ name : String, age : Int }
type alias Coach =
{ name : String, age : Int, sports : String }
Now let's write a function that gets age of the given userinfo.
getUserAge : UserInfo -> Int
getUserAge UserInfo =
UserInfo.age
Now let's create two types of users.
sam = UserInfo "Sam" 24
charlie = Coach "Charlie" 52 "Basketball"
Now let's try to get age of both of these people.
getUserAge sam
getUserAge 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.
getUserAge : { 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
.
If this blog was helpful, check out our full blog archive.