Day 1: Handling the Basics

Today, you’re going to learn the basic building blocks of the language. We’ll get through literals, lists, and basic types. Then, we’ll move toward functions and collections. Finally, we’ll close out Day 1 with higher order functions and functional composition, hallmarks of any functional language. In Day 2, you’ll learn to handle user input responsively, without resorting to callbacks. You’ll also learn to work with text and images. In Day 3, we’ll combine all of these concepts in a game.

Evan says that Elm is most strongly influenced by the ML language family. ML is an influential general-purpose functional language.[55] The syntax is heavily inspired by Haskell, and the semantics are strongly influenced by OCaml. Although some of those languages can feel academic and terse at times, you’ll find Elm practical right down to the core. Let’s dive in.

Installing Elm

Before we get started, you’re going to have to install a few tools. Go to the Elm home page.[56] There, you can find what you need to install the Elm language. We’re going to use version 0.15. You’ll also want the Elm REPL.[57], which now comes with the Elm platform. We’ll spend most of Day 1 in the Elm REPL and most of Days 2 and 3 in the Elm server.

Go ahead and fire up the REPL to see if things are working.

Simple Expressions

Let’s look at some simple expressions:

 
> 4
 
4 : number
 
> "String"
 
"String" : String
 
> 5 < 4
 
False : Bool

Everything returns a value with its type. Elm is strongly typed, and both the syntax and tools reflect this value. Let’s poke around the edges of the type system:

 
> [1, "2"]
 
[1 of 1] Compiling Repl ( repl-temp-000.elm )
 
 
 
The 2nd element of this list is an unexpected type of value.
 
 
3| [1, "2"]
 
^^^
 
...​
 
 
> 4 + "4"
 
[1 of 1] Compiling Repl ( repl-temp-000.elm )
 
...​
 
 
As I infer the type of values flowing through your program, I see a conflict
 
between these two types: number String
 
 
> "4" ++ "4"
 
"44" : String
 
 
> 4 ++ 4
 
[1 of 1] Compiling Repl ( repl-temp-000.elm )
 
...​
 
Expected Type: appendable
 
...​
 
> [1, 2] ++ [3, 4]
 
[1,2,3,4] : [number]

So Elm is strongly typed and enforces those constraints within lists and across operators. Like the type systems in Haskell and ML, Elm’s type system is strong enough to represent complex data types but flexible enough to infer and coerce those types.

Contrast these type errors to JavaScript’s behavior, where {} + [] = 0 and {} + {} = NaN. More than just an oddity, bugs like this lead to unpredictable and unstable code. At compile time, we’ll need to think a little harder about our types, but our programs will be much more reliable at runtime.

Elm types have a hierarchy, called type classes. Presently, you can’t build your own instances, but the language does include its own hierarchy of types. For example, both lists and strings are appendable data types so we can use them with the ++ operator.

 
> a = [1, 2, 3]
 
[1,2,3] : [number]

This type system is type inferred, meaning you don’t have to declare the type of every argument and every variable. The type system is also polymorphic, meaning you can treat types that inherit from the same type class the same. You will see that Elm takes full advantage of its ML and Haskell heritage to build on some of the best type systems in the world.

 
> a[1] = 2
 
[1 of 1] Compiling Repl ( repl-temp-000.elm )
 
> a[1] = 2
 
<function> : List number -> number'

Elm is a single-assignment language, and very strictly so, though in the REPL, you can redefine whole primitive values for convenience. Elm is like Elixir in this regard.

Conditionals

Elm provides some control structures, though you won’t rely on as many of them quite as often as you would in other languages. Here are a few simple control structures, starting with a simple if.

 
> x = 0
 
0 : number
 
 
> if x < 0 then "too small" else "ok"
 
"ok" : String

That statement will give you the basic one-line if. The multiline if works like a case in Ruby or a switch in Java:

 
> x = 5
 
5 : number
 
> if | x < 0 -> "too small" \
 
| | x > 0 -> "too big" \
 
| | otherwise -> "just right"
 
"too big" : String

The character \ helps when you’re running in the REPL. It means continue the statement on the next line. When you’re using pattern matching, use case. Pattern matching allows us to match the structure of some type. Here, we’re matching on the structure of a list:

 
> list = [1, 2, 3]
 
[1,2,3] : [number]
 
> case list of \
 
| head::tail -> tail \
 
| [] -> []
 
[2,3] : [number]

This statement returns the tail of a list, if it exists. Lists with at least one element match the head::tail clause, returning the tail. Empty lists match []. Now that you’ve seen some basic types, let’s build some types of our own.

Building Algebraic Data Types

The beauty and power of a type system becomes much stronger as you build your own complex data types. Take, for example, a chess piece. We need to worry about both color and piece. Use type to define a data type, like this:

 
> type Color = Black | White
 
> type Piece = Pawn | Knight | Bishop | Rook | Queen | King
 
> type ChessPiece = CP Color Piece
 
> piece = CP Black Queen
 
CP Black Queen : ChessPiece

Nice. A type constructor allows us to build new instances of a type. Our ChessPiece type consists of the characters CP, our type constructor, followed by a Color and a Piece. Now, we can use case and pattern matching to take the piece apart, like this:

 
> color = case piece of \
 
| CP White _ -> White \
 
| CP Black _ -> Black
 
Black : Color

That felt like a little too much work, but we’ll deal with an alternative shortly. Building a type that works like List is a little trickier. You need to know that Cons constructs a list, given an element and another list, like this:

 
type​ ​List​ = ​Nil​ | ​Cons​ ​Int​ ​List

This definition is recursive! Cons, which is used at compile time to define types, means construct, with head and tail arguments. We define a type of List as either:

That data type is interesting, but we can do better. We can define an abstract list, one that can hold any data type, like this:

 
type​ ​List​ a = ​Empty​ | ​Cons​ a (​List​ a)

In this case, a is some as yet undefined abstract data type. If you’re familiar with Java or JavaScript, think of a as a parametric type parameter, such as T in List<T>, with more flexibility and power. This definition defines a List of a as either:

If you want to know how a list is evaluated in Elm, look at the data type. You can represent the type for list [1, 2] as Cons 1 (Cons 2 Empty).

Now when I tell you that you can combine the head of a list with the tail, it makes sense. Cons works on types at compile time. The runtime counterpart of the Cons operator that works on data is ::, and it works just as you’d expect:

 
> 1 :: 2 :: 3 :: []
 
[1,2,3] : [number]

Elm builds the list, right on cue, and then tells us that we’re working with a list of numbers. Brilliant. We’ll dive a little more into types as we move forward. For now, let’s press on. Our chess piece was a little awkward, even if it is reminiscent of Haskell. We can do better. Let’s express a chess piece with another data type, the record.

Using Records

We built types for color and piece, and that felt pretty natural. Now, if you have a beard longer than two inches, have a personalized license plate with any form of the word “monad,” or think that I/O is for wimps, you probably like using abstract data types for everything. Carry on. For the rest of us, there’s an easier way.

Recall that our chess example got a little more complicated when we wanted to extract the color. What we really need is a way to access named fields. That thing is a record, and it’s a natural companion to JavaScript’s objects. Let’s say we want to represent a chess piece with Color and Piece fields:

 
> blackQueen = {color=Black, piece=Queen}
 
{ color = Black, piece = Queen } : { color : Repl.Color, piece : Repl.Piece }
 
> blackQueen.color
 
Black : Repl.Color
 
> blackQueen.piece
 
Queen : Repl.Piece

The Repl. is just a scope for the types, and the . notation is just sugar. .color is actually a function:

 
> .color blackQueen
 
Black : Repl.Color

Now, we can freely access the components of our structured type. As with many functional languages, records are immutable, but we can create a new one with updated fields, or even changed fields, like this:

 
> whiteQueen = { blackQueen | color <- White }
 
{ color = White, piece = Queen } : { piece : Repl.Piece, color : Repl.Color }
 
> position = { column = "d", row = 1 }
 
{ column = "d", row = 1 } : {column : String, row : number}
 
> homeWhiteQueen = { whiteQueen | position = position }
 
{ color = White, piece = Queen, position = { column = "d", row = 1 } }
 
: { piece : Repl.Piece
 
, color : Repl.Color
 
, position : { column : String, row : number }
 
}
 
> colorAndPosition = { homeWhiteQueen - piece }
 
{ color = White, position = { column = "d", row = 1 } }
 
: { color : Repl.Color, position : { column : String, row : number }
 
}
 
> colorAndPosition.color
 
White : Repl.Color

Nice. We created three new records, all of different types, by transforming our original record. We’ll come back to records after you’ve learned the greatest building block in Elm, the function.

Working with Functions

As with any functional language, the foundation of Elm is the function. Defining one is trivial. Let’s see some primitive functions:

 
> add x y = x + y
 
<function> : number -> number -> number
 
> double x = x * 2
 
<function> : number -> number
 
> anonymousInc = \x -> x + 1
 
<function> : number -> number
 
> double (add 1 2)
 
6 : number
 
> List.map anonymousInc [1, 2, 3]
 
[2,3,4] : [number]

The syntax for creating functions is dead simple and intuitive. add is a named function with two arguments, x and y. Anonymous functions express parameters as \x, and the function body follows the characters ->.

As you’ll see with Elixir, Elm lets you compose functions with the pipe operator, like this:

 
> 5 |> anonymousInc |> double
 
12 : number

We take 5, and pass it as the first argument to anonymousInc, to get 6. Then, we pass that as the first argument to double. We can also make that expression run right to left:

 
> double <| anonymousInc <| 5
 
12 : number

Evan Czaplicki, creator of Elm, says he got this feature from F#, which in turn got the idea from Unix pipes, so this idea has been around a while, but it’s a good one!

As with any functional language, there are plenty of functions that will let you work with functions in all kinds of ways:

 
> List.map double [1..3]
 
[2,4,6] : List number
 
> List.filter (\x -> x < 3) [1..20]
 
[1,2] : List comparable

[1..3] is a range. You can explore more of the List functions with the list library.[58]

When you’re composing a solution with Elm, you might be tempted to code each case as a separate function body as you would in Haskell, Erlang, or Elixir, but no luck:

 
> factorial 1 = 1
 
<function> : number -> number'
 
> factorial x = x * factorial (x - 1)
 
<function> : number -> number
 
>
 
RangeError: Maximum call stack size exceeded

It looks like the second call replaced the first. Instead, you need to use the same function body and break the problem up using case or if, like this:

 
> factorial x = \
 
| if | x == 0 -> 1 \
 
| | otherwise -> x * factorial (x - 1)
 
<function> : number -> number
 
> factorial 5
 
120 : number

Simple enough. factorial 0 is 1; otherwise, factorial x is x * factorial (x-1). You would handle list recursion the same way:

 
> count list = \
 
| case list of \
 
| [] -> 0 \
 
| head::tail -> 1 + count tail
 
<function> : [a] -> number
 
> count [4, 5, 6]
 
3 : number

The count of an empty list is zero. The count of any other list is 1 plus the count of the tail. Let’s see how to attack similar problems with pattern matching.

Pattern Matching

You can use pattern matching to simplify some function definitions:

 
> first (head::tail) = head
 
<function> : List a -> a
 
> first [1, 2, 3]
 
1 : number

Be careful, though. You will need to cover every case in your functions, or you could have some error conditions like this:

 
> first []
 
Error: Runtime error in module Repl (on line 23, column 22 to 26):
 
Non-exhaustive pattern match in case-expression.
 
Make sure your patterns cover every case!

Since head::tail doesn’t match [], Elm doesn’t know what to do with this expression. Using a nonexhaustive pattern match is one of the few ways you can crash an ML-family language and it’s totally avoidable.

Functions and Types

We quickly glossed over the types of functions. It turns out that Elm is a curried language:

 
> add x y = x + y
 
<function> : number -> number -> number

Notice the data type of the function. You might have expected a function that takes two arguments of type number and returns a type of number. Here’s how currying works. Elm can partially apply add, meaning it can fill in one of the two numbers, like this:

 
> inc = (add 1)
 
<function> : number -> number

We just created a new partially applied function called inc. That new function applies one of the arguments for add. We filled out x, but not y, so Elm is basically doing this:

 
addX y = 1 + y

Currying means changing multi-argument functions to a chain of functions that each take a single argument. Now, we can handle the currying ourselves. Remember, curried functions can take no more than one argument at a time:

 
> add x y = x + y
 
<function> : number -> number -> number
 
> add 2 1
 
3 : number
 
> (add 2) 1
 
3 : number

That’s slick. We defined add again, for reference. Then we added 2 and 1, without currying. Then we curried the function ourselves, creating a function that adds two to the first argument, and passed that function a 1.

Whew.

Fortunately, you won’t usually need to do the currying because Elm will do it for you, but you can use partially applied functions to create some cool algorithms. Let’s go back to add.

Elm infers that you’re going to be doing arithmetic with numbers. Elm uses the type class number because that’s the type class the + operator supports. You aren’t limited to integers, though:

 
> add 1 2
 
3 : number
 
> add 1.0 2
 
3 : Float
 
> add 1.0 2.3
 
3.3 : Float

So Elm is polymorphic. It figures out the most general type that will work, based on your use of operators. In fact, you can see the same behavior with the ++ operator:

 
> concat x y = x ++ y
 
<function> : appendable -> appendable -> appendable

Elm assumes the function uses two appendable elements, like this:

 
> concat ["a", "b"] ["c", "d"]
 
["a","b","c","d"] : [String]
 
> concat "ab" "cd"
 
"abcd" : String

That’s polymorphism. As you might expect, you can use polymorphism with points, too. Let’s say I have a point and want to compute the distance to the x-axis. That’s easy to do:

 
> somePoint = {x=5, y=4}
 
{ x = 5, y = 4 } : {x : number, y : number'}
 
> xDist point = abs point.x
 
<function> : {a | x : number} -> number
 
> xDist somePoint
 
5 : number

Elm’s type inference infers that x and y are numbers within a record. Now, I can pass it any point:

 
> twoD = {x=5, y=4}
 
{ x = 5, y = 4 } : {x : number, y : number'}
 
> threeD = {x=5, y=4, z=3}
 
{ x = 5, y = 4, z = 3 } : {x : number, y : number', z : number''}
 
> xDist twoD
 
5 : number
 
> xDist threeD
 
5 : number

Alternatively, I could use pattern matching, like this:

 
> xDist {x} = abs x
 
<function> : { a | x : number } -> number
 
> xDist threeD
 
5 : number

We’re using matching to pick off the x field, and the rest of the example works the same way. The point is that records are fully polymorphic too. Elm doesn’t care that the records we use are the same type. It only needs the record to have an x field. You’re seeing the power of a type system that will do its best to catch real problems but that will get out of the way when there isn’t one.

That’s probably enough for Day 1. Let’s wrap up what we’ve done so far.

What We Learned in Day 1

We’ve taken a quick pass through Elm. We found a functional language that has many of the attributes of functional languages in the ML family, tweaked to work on the web. We spent extra time exploring basic pattern matching and working with various aspects of functions. We looked at several ways of composing functions and even looked at how function currying and partial application works.

Your Turn

Elm is a younger language than most of the others in this book. Most of the documentation you’ll find is pretty consolidated on the Elm language page, or links off that page. I expect that to change quickly.

Find…

Do (Easy):

Do (Medium):

Do (Hard):

That’s it for Day 1. Tomorrow, we’re going to leave the REPL and dive into web applications. We’ll explore the basic concept of functional reactive programming. Most of the ideas we’ll see involve using signals, which express changing values over time as functions. You’ll also see how to combine signals with functions. Then, we’ll learn to display text and images.