Types

Believe the type

moo

Previously we mentioned that Elm has a static type system. The type of every expression is known at compile time, which leads to safer code. If you write a program where you try to divide a boolean type with some number, it won’t even compile. That’s good because it’s better to catch such errors at compile time instead of having your program crash. Everything in Elm has a type, so the compiler can reason quite a lot about your program before compiling it.

Unlike Java or Pascal, Elm has type inference. If we write a number, we don’t have to tell Elm it’s a number. It can infer that on its own, so we don’t have to explicitly write out the types of our functions and expressions to get things done. We covered some of the basics of Elm with only a very superficial glance at types. However, understanding the type system is a very important part of learning Elm.

A type is a kind of label that every expression has. It tells us in which category of things that expression fits. The expression True is a boolean, "hello" is a string, etc.

Now we’ll use elm-repl to examine the types of some expressions. We’ll do that by typing expressions into elm-repl. After each valid expression, the repl tells us its type. Let’s give it a whirl.

TODO: Add a section on elm-repl, or come up with a different way to accomplish this in the online editor. Maybe type holes?

> 'a'
'a' : Char
> True
True : Bool
> [1, 2]
[1,2] : List number 
> (True, 'a')
(True, 'a') : ( Bool, Char )
> 4 == 5
False : Bool

bomb Here we see that typing an expression prints out the value of that expression followed by : and its type. : is read as “has type of”. Explicit types are always denoted with the first letter in capital case. 'a', as it would seem, has a type of Char. It’s not hard to conclude that it stands for character. True is of a Bool type. That makes sense. Examining the type of [1, 2] yields a List number. The lowercase n in the type of number indicates a built-in type variable. We’ll get to type variables later in this chapter. A number can be either an Int or a Float, and will concretely become one or the other depending on how it’s used. Unlike lists, each tuple length has its own type. So the expression of (True, 'a') has a type of ( Bool, Char ), whereas an expression such as ('a','b','c') would have the type of ( Char, Char, Char ). 4 == 5 will always return False, so its type is Bool.

Functions also have types. When writing our own functions, we can choose to give them an explicit type declaration. This is generally considered to be good practice. From here on, we’ll give all the functions that we make type declarations. Remember the first function we made previously that doubles a number? Here’s how it looks like with a type declaration.

doubleMe : number -> number
doubleMe x = 
    x + x

doubleMe has a type of number -> number, meaning that it maps from a number to a number. That’s because it takes one number as a parameter and returns another as a result. We didn’t have to give this function a type declaration because the compiler can infer by itself that it’s a function from a number to a number but we did anyway. But how do we write out the type of a function that takes several parameters? Here’s a simple function that takes three integers and adds them together:

addThree : Int -> Int -> Int -> Int
addThree x y z = 
    x + y + z

The parameters are separated with -> and there’s no special distinction between the parameters and the return type. The return type is the last item in the declaration and the parameters are the first three. Later on we’ll see why they’re all just separated with -> instead of having some more explicit distinction between the return types and the parameters like Int, Int, Int -> Int or something.

If you want to give your function a type declaration but are unsure as to what it should be, you can always just write the function without it and then check it in elm-repl. Functions are expressions too, so it works on them without a problem. To write a multi-line expression in elm-repl, add a backslash to the end of each line you want to continue on the line below, like this.

> addThree x y z = \
|     x + y + z
<function> : number -> number -> number -> number

Here’s an overview of some common types.

Int stands for integer. It’s used for whole numbers. 7 can be an Int but 7.2 cannot. Int is bounded, which means that it has a minimum and a maximum value. The maximum possible value of Int is 2147483647 and the minimum is -2147483648.

Float is a floating point with the same properties as JavaScript floats.

circumference : Float -> Float
circumference r = 
    2 * pi * r
toPrint = circumference 4.0
25.132742

Bool is a boolean type. It can have only two values: True and False.

Char represents a character. It’s denoted by single quotes.

String represents a string. It’s denoted by double or triple quotes and can contain many characters. Triple quoted strings are string literals that can span across multiple lines, with line breaks and whitespace being preserved in the string.

Tuples are types but they are dependent on their length as well as the types of their components, so there is theoretically an infinite number of tuple types, which is too many to cover in this tutorial. Note that the empty tuple () is also a type which can only have a single value: (). This value is read as "unit" and is the common way to denote an empty value with no specific meaning.

Type variables

What do you think the type of the List length function is? Well, length takes a list of any type and returns its length, so what could it be? Let’s check!

> List.length
<function> : List a -> Int

box Hmmm! What is this a? Is it a type? Remember that we previously stated that types are written in capital case, so it can’t exactly be a type. Because it’s not in capital case it’s actually a type variable. That means that a can be of any type. This is much like generics in other languages, only in Elm it’s much more powerful because it allows us to easily write very general functions if they don’t use any specific behavior of the types in them. Functions that have type variables are called polymorphic functions. The type declaration of length states that it takes a list of any type and returns an Int representing the length of that list.

Although type variables can have names longer than one character, we usually give them names of a, b, c, d …

Earlier in this chapter we mentioned the number type variable. This is a type variable built-in to Elm that specifically represents either an Int or a Float, but nothing else. There are other bult-in type variables representing other important things. comparable represents types that can be compared for equality with the == operator. This can be String, Char, Int, Float, Time, or a list or tuple containing only comparable values.

toPrint = 4 == 5
False

appendable represents items that can be appended to each other with the ++ operator. These are String, List, and Text.

toPrint = "Hello" ++ "!"
"Hello!"

Remember Tuple.first? It returns the first component of a pair. Let’s examine its type.

> Tuple.first
<function> : ( a1, a2 ) -> a1

We see that first takes a tuple which contains two types and returns an element which is of the same type as the pair’s first component. That’s why we can use first on a pair that contains any two types. Note that just because a1 and a2 are different type variables, they don’t have to be different types. It just states that the first component’s type and the return value’s type are the same.