Being able to read type signatures in Elm will greatly improve your development experience. In this article we will learn how to do just that.
Elm has what is called "type inference". This means that if you don't tell Elm what kind of types your program uses it will figure it out anyway. We can see this at work if we drop into the Elm REPL in our favorite terminal.
$ elm repl ---- Elm 0.19.0 ---------------------------------------------------------------- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc. -------------------------------------------------------------------------------- > "Hello" "Hello" : String > 155 155 : number > False False : Bool
The lines starting with
> is our input and the other lines are output.
We see that when we write
"Hello", Elm replies with
"Hello" : String.
We can read the
: as "has type", which then gives us
"Hello" has type
155 has type
False has type
Now let's try some functions.
> greet name = "Hello, " ++ name <function> : String -> String > addOne x = x + 1 <function> : number -> number
If we read the
-> as "to", we get
greet is a function that has type String to String.
A bit less formally:
greet is a function that takes a String and returns a String.
addOne is a function that has type number to number.
And for functions that take multiple arguments:
> printAge name age = name ++ " is " ++ String.fromInt age ++ " years old." <function> : String -> Int -> String
printAge is a function that has type String to number to String.
printAge is a function that takes a String and a number and returns a string.
It may look a bit unusual that each argument in the type signature is separated by an arrow. It is this way because of partial application, which we will get back to in a later post. If you read the Wikipedia article it might seem scary and overly mathematical but it is actually incredibly useful! For now you only have to remember that the rightmost value is the one being returned.
Even though Elm can infer the types in our program, it is still very useful to annotate functions and values with types manually. Doing this serves two purposes:
- Documentation – when looking at a piece of code it often helps to know exactly what type of values the function is working with.
- Easier implementation – when the compiler knows what you are trying to do, it can give you even better error messages if you have an error in your code
It is recommended to always have type signatures on all top-level values and functions. In fact, if you are trying to publish a package on https://package.elm-lang.org/ you are required to have type signatures on all exposed values.
If you have read some Elm code, tutorials or even tried to write some Elm yourself, you may have seen type signatures that have lower-case letters in them.
Often, these are single-letter names like
c etc., but you may also have seen types like
What is significant about these is that they start with lower-case letters.
This tells the compiler that they are type variables.
To figure out what they are we can look at the
update function from The Elm Architecture.
Its signature (from
update : msg -> model -> model
If we apply what we have learned thus far we can tell that
update is a function that takes some
msg and a
model and returns a
model start with lower-case letters they are type variables.
This means that they can be anything you want as long as all the occurrences of a variable is of the same type.
So the return type of the variable is the same type as the second argument you give to the function.
Usually in an Elm application you have a custom type for messages called
Msg and a type alias for a record that is used for the application state called
In this case the concrete type for
update would be
update : Msg -> Model -> Model
model are type variables they could just as well be
update : String -> String -> String
msg can be a custom type called
model a type alias called
update : Power -> Metal -> Metal
So, we know that all occurrences of a type variable in a signature has to be the same type. What then can you tell me about this function?
identity : a -> a
Well, since all functions in Elm (outside of the
Debug module) are pure functions there is only one possible implementation for
We know that its return type is the same as the argument given for all types in the whole wide world.
Further, since the implementation can't possibly know anything about the argument, the only way to implement this function is to _return the argument_!
identity : a -> a identity x = x
When reading and writing Elm code it really helps to understand how to read and write type signatures. They often tell you what a function does without having to read its implementation or documentation and they make the compiler give you better error message if you make mistakes.