Table Of Contents

1. - Table Of Contents -

2. Introduction

3. The Elm Architecture Part 1

4. The Compiler as Assistant

5. Functions

6. if then else, case

7. let

8. Lists

9. Comments

10. elm-repl

11. elm-package

12. elm-reactor

13. elm-make (and Webpack)

14. elm-format

15. elm-live

16. elm-test

17. Types

18. Tuples

19. Records

20. Type Aliases

21. (Union) Types

22. How to learn Elm

23. The Elm Architecture Part 2

24. HTML (and SVG)

25. Mouse and Keyboard

26. Lists

27. Array

28. Dicts

29. Strings in Elm

30. Characters in Elm

31. Regular Expressions

32. Debugging

33. Randomness

34. Time

35. Dates

36. Ports

37. Using Elm within React (and Webpack)

38. JSON Decoding

39. Onward

Introduction

It is an almost impossible task to write a great book on programming. It requires not just skill and effort on the part of the author, but a rare alignment of experience, aptitude and interest with the reader. A dry reference is possible but of limited value. A tutorial unlikely to be pitched at the right level. This book tries something different in its goal of teaching the core of the Elm programming language and ecosystem.

It is not primarily a book. It is (available as) a Flash Card App and has associated resources, quizzes, audio content and more online.

It focuses on the most useful details. You absolutely should work through real examples, but these should come from the demands of your own project or failing that in a structured, challenge based way (I recommend sources for these).

It targets fluency not superficial knowledge, but includes the hard parts and tools to make learning them reasonable.

I want to make it possible for you to learn Elm more quickly and more thoroughly than is otherwise possible.

Audience

You should already know at least one mainstream programming language. But you may not be familiar with functional programming or have studied computer science formally. If you have never programmed before this is a terrible place to start. If you are familiar with functional programming, particularly ML languages, you can probably skip or skim the more conceptual parts.

Elm

Often programmers will talk of trade offs between languages. That one 'Serious' language is hard, verbose but performant and that other little language is easy, quick, dirty but slow. (I have examples in mind, but sharing them seems ill advised!) Don't believe them. It is 2018 and you should demand all the things. Languages like Elm, Kotlin and Swift reject these kinds of false choices and give us concise, clear code, performance, safety and clarity. They are simply better than many common alternatives.

Elm is the friendliest language I've ever encountered and has great tools today. It combines theory with pragmatism, looks beautiful and is remarkably useful in the right context. As long as you want to create applications in a web browser it is likely to be a great choice, particularly if it is somewhat complex with many different interactions or updates. But even if you don't end up using it in production, just learning will give you a much better feel for modern Javascript approaches to state like Redux.

How to use this book

This book consists of lots of concise chapters. A small number target core ideas or examples. Most focus on a topic and give a concise but clear description. Ideally you would mainly use the Flash Card App to drive learning from this book, looking up things you are struggling with. On the Elm Accelerated website you will find more resources like quizzes or audio summaries. These provide additional tools to accelerate your learning.

Install Elm

Before continuing you should probably install Elm (if you haven't already). Go to elm-lang.org and follow the instructions there for your operating system. You will also want to get the Elm plugins for your text editor of choice (Visual Studio Code support is great, but similar tools exist for Atom and Sublime). It is also worth getting the elm-format tool and configuring your editor to format your code on save.

Try Elm

Once you've done that why not try out a few simple commands in the elm-repl. Don't worry if you don't know exactly what is going on (this book will teach you), but notice how you can evaluate simple expressions and create functions with very little noise. Notice also how unusually friendly and helpful any error messages are. This is one of the many areas where Elm is way ahead of the curve.

> elm-repl

> 1 + 1

2 : number

> 4 > 5

False : Bool

> if True then "hi" else "bye"

"hi" : String

> f x = x * x

<function> : number -> number

> f 4

16 : number

> f "hi"

-- TYPE MISMATCH ------------------------

The argument to function `f` is causing a

mismatch.

3|   f "hi"

^^^^

Function `f` is expecting the argument to

be:

number

But it is:

String

The Elm Architecture Part 1

Elm is not just a language. It supplies an architecture for creating front end web applications. This architecture is a bit like combining React, Redux, flow, Prettier and a lot of self discipline. Except unlike in the Javascript world, it all comes out of the box: you don't have to make dozens of decisions for each project, you just get started and everything works wonderfully together.

We start Elm with a beginnerProgram which provides simplified version of the Elm architecture. We specify a model, a set of updates, a update function and a view. It is is a bit like pairing React and Redux but without the boilerplate. So what does that look like in practice?

The canonical trivial example program for modern front end frameworks is a counter (the non trivial, a to do app). Let's see that in Elm.

module Example exposing (main)

import Html exposing (Html, button, div, text)

import Html.Events exposing (onClick)

main =

Html.beginnerProgram { model = 0

, view = view

, update = update

}

type Msg

= Increment

| Reset

update msg model =

case msg of

Increment ->

model + 1

Reset ->

0

view model =

div []

[ div [] [ text (toString model) ]

, button

[ onClick Increment ]

[ text "+" ]

, button

[ onClick Reset ]

[ text "Reset" ]

]

After a tiny amount of boilerplate we (explicitly) import some functions. Then we describe our main program which uses the beginnerProgram structure. We set up our initial model (in this case it is trivial, the number 0), the view function and how updates work. Each of these is a function.

Updates happen via a function which takes a message and existing model, then returns the updated model. In this case either incrementing the number or setting it to 0. Notice how we don't change the existing data we return a new model.

The model itself uses parts of the Elm Html library to describe the view and allow for triggering of updates. There is no special syntax here just pure Elm code. Each of the Elm HTML functions take a list of attributes and then a list of children.

We will look at all of these ideas in more detail. If you are familiar with using React + Redux + Immutable.js you might already notice how much shorter and cleaner this is than the equivalent there. But there are many other benefits.

The Compiler as Assistant

One of the amazing things about Elm is how the compiler acts as a kind of robot assistant. In place of cryptic error messages you get clear, actionable advice or suggestions about what has gone wrong or needs to be added. Expect to be impressed with just how helpful the tools are. This is particularly valuable when learning, refactoring or adding new features.

In action

Let's open up the example from the previous chapter. I'm using Visual Studio Code with elm-format set up to reformat the code on save. But you can get a similar experience with many other tools.

Immediately I'm getting three warning messages about the code. Let's look at the first.

Top-level value `main` does not have a type annotation. - I inferred the type annotation so you can copy it into your code:

main : Program Never number Msg

Okay let's add that annotation as suggested:

main : Program Never number Msg

main =

Html.beginnerProgram { model = 0

, view = view

, update = update

}

Now the function has type annotations. We will look at these in more detail later but notice how I didn't have to spend time thinking about the types, the compiler told me the answer and will now provide an extra level of security on refactoring. I simply won't be able to mess these up (without a compiler error).

Okay let's do the other two:

update : Msg -> number -> number

update msg model =

and

view : a -> Html Msg

view model =

actually we can be a little more specific on that last one:

view : number -> Html Msg

view model =

as the compiler could only see the model being used by toString to could not be more specific.

Adding a feature

Let's add a new button which multiplies the count by itself. First let's add the it to our update type

type Msg

= Increment

| Square

| Reset

We now get the following message on the update function:

This `case` does not have branches for all possibilities. - You need to account for the following values:

Example.Square

Add a branch to cover this pattern!

So let's add this case to the update function

update : Msg -> number -> number

update msg model =

case msg of

Increment ->

model + 1

Reset ->

0

Square ->

model ^ 2

I'm using the Elm syntax ^ to square a number, but you could also do

Square ->

model * model

Now let's add a button:

view : number -> Html Msg

view model =

div []

[ div [] [ text (toString model) ]

, button

[ onClick Increment ]

[ text "+" ]

, button

[ onClick Square ]

[ text "Square" ]

, button

[ onClick Reset ]

[ text "Reset" ]

]

And we're done. If we had made any (well nearly any) mistake the compiler/tooling would warn us.

This assistance becomes even more valuable as code gets more complex. As humans we can struggle to keep large, complex programs in our heads; with Elm we don't need to.

Some of the details of the syntax might not make sense yet, but we are going to focus on that next.

Functions

Elm combines a clear and concise function syntax with a sophisticated strong type system. Functions require a lot less ceremony than most languages and consist of a name, arguments an equals sign then an expression to be evaluated.

As we have seen declaring a function is ridiculously simple (try these and similar examples in elm-repl if you are not already familiar with the ideas, but then you can skip this chapter).

f x = x + 1

That is pretty much as short and clear as it is possible to be. What about something with another argument?

add x y = x + y

That seems just a simple and clear. Well it is (on the surface); however, there is something sophisticated going on here. We haven't created a function with two arguments. Indeed, if we type that out in elm-repl we find that the signature is:

<function> : number -> number -> number

Or a function which takes a number and returns a function which takes a number and returns a number. Confused? Yeah, this doesn't seem very clear. But this allows for some really interesting things.

To start with we can partially apply it.

increment x = add 1 x

decrement x = add -1 x

Now we've created two new functions built upon the old function. Try that in Javascript! (Using ES6 arrow functions you can do something a bit similar, but with a lot more noise around actually invoking them.)

But in practice you usually don't need to worry about this. Just write out the function roughly as you might in other languages (but with less ceremony) and the compiler will tell you if you make any mistakes.

Brackets

If you comes from a C-like language such as Javascript you may find the lack of brackets confusing. They still play a key role in Elm: to enforce what is executing on what (although they should also be used to make code more readable). But unlike Javascript, they aren't wrapped around function arguments.

if then else, case

In many languages there is an if keyword. Elm has an if but it must go along with an else as being a pure, functional language a standalone if doesn't make much sense: without side effects we have to return something otherwise we haven't accomplished anything.

abs x = if x >= 0 then x else -x

We can also compose more than one such expression

f x = if x > 2 then

2

else if x < -2 then

-2

else

0

However, in functional languages like Elm we are often more interested in doing pattern matching. Taking an argument and then doing one of many alternatives depending on which pattern matches. We will be getting to a more in depth discussion of types shortly (where the full power of this feature will become apparent), but for now recall the update function from the Elm architecture.

We typically use a case statement to deal with this. Given a message one of many updates should be applied.

case msg of

Increment -> model + 1

Decrement -> model - 1

Reset -> 0

The common in many languages while is not part of Elm.

let

When writing more complex Elm functions we often want to create some local or intermediate values. The let expression allows us to do exactly this.

An example should make things clear.

f x =

let

y = x * x

z = x + 2

in

y + z

These local values can include functions which are then applied in the in part.

We use let to set up some local bindings which can then be applied.

Lists

Lists look a lot like Arrays in many other languages though this can be misleading. Lists are fundamentally a first item (the head) along with the tail which is itself a (possibly empty) list.

[1,2,3]

The above is also expressible as

1 :: [2,3]

or even

1 :: 2 :: [3]

1 :: 2 :: 3 :: []

This allows us to use the case expression to write recursive list functions. For example.

map f lst =

case lst of

h::t -> (f h) :: map f t

[] -> []

We can also append lists with ++

> [1,2,3] ++ [4,5,6]

[1,2,3,4,5,6] : List number

Now we will return to types and data structures after reviewing the main Elm tools. It is also worth noting Elm has an Array structure as a core module.

We use :: to create a list out of leading element and rest.

Comments

Elm has the usual single and multiline comments, though the syntax is quite different to Javascript based on either minus signs or curly brackets.

-- Single line comment

{- Multiline comment

{- can be nested -}

-}

To document functions you can do the following beforehand:

Documentation comments start with {-|.

{-| This is special documentation comment

of example Notice the vertical bar

example 2 == 3

-}

example : Int -> Int

example n = n + 1

When you are creating packages you should review the Full comment documentation for further details, but for now the above should be good enough.

elm-repl

You have hopefully been using Elm Repl quite a bit to explore Elm. One of the most useful features is how it will give you immediate feedback on the types of whatever you have typed (or errors if you have made any).

The less obvious things include taking a new line (use a \).

f x = \

x + 1

And importing modules

> import String

> String.toUpper "hi world"

"HI WORLD" : String

This can include your local modules. Just import it by name as you would a core Elm library.

elm-package

Elm Package is Elm's dependency management system. It is unusually polite. After figuring out a solution to your request it will ask you for confirmation before changing anything.

We use elm-package to install dependencies (also to publish them, but you can look that up later). To install a specific package elm-package PACKAGE and with version: elm-package PACKAGE VERSION. -y will skip the polite confirmation prompts.

elm-reactor

Elm Reactor is a great tool for Elm only development. It starts a local server which allows you to open Elm programs. Errors are nicely presented and it is a great way to get started.

As of Elm 0.18 it has amazing debugging support. The only thing you are likely to want to configure is the port used with a -p PORT.

elm-make (and Webpack)

Elm Make is the Elm tool for building projects. To be honest it is currently quite basic and will not do things like minification of the output. You will probably want to use Webpack in any substantial Elm projects. However for simple things it provides a straightforward way to build something deploy-able. Via extensions of an explicit output you can control the target. In the latter case you will need to include it in an HTML file in the standard way.

elm-make Main.elm --output main.html

elm-make Main.elm --output elm.js

elm-format

There isn't that much to say about Elm format except that it is excellent. You should configure your editor to format your code on save with it. That way you avoid wasting time on trivial formatting decisions or inconsistencies. Even Javascript developers are doing it now with Prettier.

One thing is worth noting here. elm-format does some slightly unconventional stuff around delimiters. Its policy is to minimise version control diffs. Which seems pretty sensible. It also uses a lot of (vertical) whitespace, which sometimes feels wasteful. But in return, nearly any Elm code you look at will be formatted consistently. Even if it makes some minor choices you disagree with, it is a small price to pay.

elm-live

Elm Live gives a pleasant live reload enabled development experience.

It is installable via npm npm install -g elm-live. The flags you will probably want are: --pushstate for allowing client side routing on the url, --debug for the debugger, --dir=PATH (to specify the base directory for static content) and --open to open it in your web browser.

elm-test

On the one hand testing is just not as important in Elm as a many other languages. The Elm compiler ensures that your code does something valid which makes many of the tests we might want to do in dynamic languages redundant. The Elm architecture ensures that states and updates are clean and transparent. But tests are still important. The good news is that Elm has excellent unit testing abilities and the functional nature of Elm together with the clean update mechanism makes everything so much easier to test than most other environments.

Elm also allows for easy Fuzz testing (testing with randomly generated inputs) which can give us further confidence in our code.

To get started install elm-test with

npm install -g elm-test

In an Elm project initialise with

elm-test init

then copy all of the dependencies from your main elm-package.json into the tests folder version. You can now run elm-test.

elm-test is based around just 4 concepts (only 3 of which you need to know about initially): describe, test and Expect.

You create test suites out of describe functions. This just groups tests with a string description. You can nest lists of describe lists. These lists can contain test invocations.

You use describe to group tests in Elm Test.

A test typically consists of the test function, a string description (the first argument) and a second argument, an anonymous function (Elm is eagerly evaluated so this allows the test to be run separately). In the anonymous function you write a test which will somewhere have an expectation, via the Expect module.

Let's look at a simple example (but bear in mind even tests in complex projects will follow a similar pattern as we will basically be testing that given an input a function produces an output).

trivialSuite : Test

trivialSuite =

describe "really simple test"

[ test "custom sum" <|

\_ -> let

sum = List.foldl

(\n s -> n + s)

0

[1,2,3]

in

Expect.equal 6 sum

]

There are less common expectations for dealing with floating point mathematics, inequalities and so on. But Expect.equal will often be sufficient.

Types

Elm has some fairly standard built in types and ways of writing them. Strings, integers and floating point numbers look like most other programming languages. Elm is strict with types and where other languages might let you convert from integer to floating point number, Elm will demand you are explicit.

Let's take a look with elm-repl

> "hi"

"hi" : String

> 'h'

'h' : Char

> 1.0

1 : Float

> round 2.718281828459

3 : Int

> """ This is a \

| multiline\

| string"""

> String.toList "hello"

['h','e','l','l','o'] : List Char

> String.fromList ['h', 'i']

"hi" : String

These have available many of the standard operations you will have seen in other languages. Though notice how we use // for integer division and / for floating point division.

> 4 % 3

1 : Int

> 4 / 3

1.3333333333333333 : Float

> 4 // 3

1 : Int

> 4 > 3

True : Bool

> rem 4 3

1 : Int

To divide a Float by another use /.

number and comparable

Elm has a couple of special and more general types. These make defining calculations and comparisons more straightforward. This allows the definition of more general, reusable functions but Elm is still very smart about type checking. For example

> f x y = x > y

<function> : comparable -> comparable -> Bool

> f "hi" 2

-- TYPE MISMATCH ----------------------------

The 2nd argument to function `f` is causing a

mismatch.

3|   f "hi" 2

^

Function `f` is expecting the 2nd argument to

be:

String

But it is:

number

Hint: I always figure out the type of

arguments  from left to right. If an

argument is acceptable when I check it,

I assume it is "correct" in subsequent

checks. So the problem may actually be

in how previous arguments interact with

the 2nd.

> f "hi" "there"

False : Bool

> f 3 4

False : Bool

> f 4 3

True : Bool

Tuples

The most basic way of combining types in Elm is via a Tuple, which has a simple lightweight syntax that may be familiar to you from Python.

> (1,2,3)

(1,2,3) : ( number, number1, number2 )

> (1,2,3)

(1,2,3) : ( number, number1, number2 )

> ("hi", round 2, 2.32)

("hi",2,2.32) : ( String, Int, Float )

The key difference in Elm is that Tuples, like everything else, are strongly typed and the compiler will be able to warn you if you make a mistake. These Tuples can be used with functions both as return values and in arguments.

> f x = (x, x+1, "hi")

<function> : number -> ( number1

, number

, String

)

> conditional (cond, a, b)\

= if cond then a else b

<function> : ( Bool, a, a ) -> a

> conditional (False, 1, 2)

2 : number

Although the last example might be better expressed as

conditional cond a b =

if cond then a else b

Tuples are often useful when a function should return multiple arguments. To extract a specific element you can do e.g.

> Tuple.first (1,2)

1 : number

> Tuple.second (1,2)

2 : number

There is however no Tuple.third as it is considered bad practice to be needing to extract many different values from a Tuple, an alternative approach such as using Records (coming up next) is preferred. However pattern matching on larger tuples is fine.

> case (1,2,3) of \

|  (x,y,z) -> x + y + z

6 : number

The simplest way to create a composite value (perhaps with elements of different types) is with Tuples.

Records

Records are a set of keys with values. Like objects in Javascript or dictionaries in many other languages. Elm has special syntax, a name preceded by a full stop to access individual items.

> r = {a=1, b=2}

{ a = 1, b = 2 } : { a : number, b : number1 }

> r.a

1 : number

> r.b

2 : number

> (.a) r

1 : number

Unlike those languages records are immutable (there is no way to update an item in an existing record). What you do instead is create a new record from the old one; Elm has special syntax for this.

> {r | b = 3}

{ a = 1, b = 3 } : { a : number1, b : number }

This is all very lightweight and concise but completely immutable and type safe: it gives you the benefits of both dynamic languages and strongly typed static ones.

We can use let statements to destructure (concisely extract fields from) Records.

> s = {condition = True, text = "hi", count = 4}

{ condition = True, text = "hi", count = 4 }

>  let \

|   {condition, text} = s \

| in \

|   if condition then text else "fallback"

"hi" : String

And create functions which also destructure Records.

> g {count} = count + 1

<function> : { a | count : number } -> number

> g s

5 : number

Type Aliases

Tuples and Records provide are strongly typed and safe. But you wouldn't want to write them out explicitly all the time. Type aliases allow us to clearly and concisely create compound types.

> type alias User = { name : String

, surname : String

, email: String }

Type aliases must start with an upper case letter.

When we define a type alias we also get a constructor function, which allows us to quickly create items with that type by providing the elements in the appropriate order.

> User "James" "Porter" "james@email.com"

{ name = "James"

, surname = "Porter"

, email = "james@email.com"

} : Repl.User

We can also create a new item of the type with the same update syntax as for records.

u = User "James" "Porter" "james@email.com"

{ name = "James"

, surname = "Porter"

, email = "james@email.com"

}

{ u | name = "Andrew"}

{ name = "Andrew"

, surname = "Porter"

, email = "james@email.com"

}

(Union) Types

When programming in most languages we often struggle to capture a concept in a model. We want something which is specific enough to capture the details, but which is also variable enough to cope with potentially very different scenarios. Consider making an HTTP request. If it succeeds we may be primary interested in a message body. If it fails the error code and message. But in many languages there is now good way of capturing this at all. Or if we can no good way of ensuring we cover all our bases. Elm provides a wonderfully concise and safe way of tackling this kind of problem.

Let's look at it in elm-repl. Let's say we want to capture the idea of having a value which may or may not exist. Well we can do this trivially in Elm.

> type Possibly a = None | Actual a

> v = None

None : Repl.Possibly a

> u = Actual 5

Actual 5 : Repl.Possibly number

> case v of \

|   None -> "Nothing" \

|   Actual n -> "Value: " ++ toString n

"Nothing" : String

> case u of \

|   None -> "Nothing" \

|   Actual n -> "Value: " ++ toString n

"Value: 5" : String

Elm will now check we avoid the 'billion dollar mistake' of null values, ensuring that whenever we write a function operating on a Possibly type that we cover both cases. In fact this is already a core part of Elm. It is actually available as:

type Maybe a = Nothing | Just a

In creating web applications we might often have something like the following. We want to make a request to a backend. In the mean time the state is Loading. When we get a response it will either be successful with some data, otherwise an Error. But in Elm this is trivial, concise, clear and safe.

type alias Item = { title: String

, content: String

}

type State = Loading

| Data List Item

| Error String

Now we can work with the state of an application in an incredibly clear way:

case state of

Loading -> [...]

Data items -> [...]

Error text -> [...]

Elm will ensure we cover all of the possible cases.

Another core Elm type is Result. Again this is almost absurdly concise and yet something not available in most languages.

type Result error value = Err error | Ok value

Amazingly this chapter was the final key piece of Elm theory (and actually most of it was pretty practical). The remainder of this book is focused on applying the preceding powerful, but small, set of concepts to writing concise, clear, safe code to solve real problems.

How to learn Elm

I recommend a three pronged approach to learning a programming language.

1. Conceptual

Well you can start with this book along with its interactive resources. But there are many other resources available.

To start with see elm-lang.org, the official Elm website. It has an interesting and informative blog along with documentation and a web based editor to try things out.

The documentation includes a downloadable introduction written by Evan Czaplicki (the creator of Elm) along with details of all the core and community libraries.

At the time of writing there are few traditional books published on Elm. Richard Feldman's Elm in Action is due to be published in early 2018, although some of it is already available (and I highly recommend it, particularly if you are coming from a Javscript background).

egghead.io have a free, short Video course on Elm. The Pragmatic Studio have A few free video lessons.

Currently the best advanced resources on Elm are from Elm specific conference talks, in particular those from Evan Czaplicki and Richard Feldman.

2. Examples and Coding Challenges

One great set of programming challenges in Elm (along with a nice set up and tests) is from Exercism. At the time of writing there are 30 available challenges. While some are abstract many are really quite analogous to typical real world problems. The community allows sharing and discussion of solutions if you get stuck. There are various other programming challenges (or 'Kata') available in Elm, though none I've seen have been as nicely set up as Exercism.

3. Real Projects

But the key area to focus on is some kind of real project(s). Pick something(s) you are interested in (and where Elm is a good solution) and build a solution in Elm. And release it. All of these aspects are important. Obviously, without interest you won't be motivated. But you should also look for something where Elm is a good solution. If you pick something too simple then you won't benefit from the many advantages of Elm. Three options which I've found work well are:

1. A in-browser presentation with custom transitions and Elm code in slides (See Slideshow example in Elm.)

2. A minimalist game. Try to recreate a classic game or original idea in Elm. This will likely involve interactivity, somewhat complex state and updates.

3. Custom data visualisation using SVG primitives (data visualisation works well as it seems to always involve adding or changing lots of features, a major strength of Elm. It can involve complex transitions and data, again strengths of Elm.)

It is important you actually release it (even if quietly). Otherwise you won't really understand the tradeoffs of Elm versus whatever you use right now. And zero runtime errors isn't much of a benefit if there is no runtime.

While Elm 0.18 makes it really easy to get started and learn, it requires some extra tooling around deployment, even for fairly simple cases. I have an example project which is A simple Elm game together with single command, optimised deployment. We will look at this project in a later chapter. Elm 0.19 will likely significantly improve these aspects. We will also look at integrations with webpack and Javascript code in later chapters.

Open source, substantial but approachable projects are a little hard to find given Elm is quite new. And as you will likely be pairing Elm with a backend language or existing system may not be so applicable anyway. However a great recent example from Richard Feldman is this Medium Clone (in Elm) (which is an Elm version of a project to create real world examples of web frameworks).

The Elm Architecture Part 2

In the next few chapters we will look at a somewhat substantial Elm application (a game) which uses the full Elm Architecture. You can download the full source code from Example Elm Game. It includes a simple development set up and static deploy.

A full Elm Html program consists of four pieces: an initial state, a function from state to view, an update function (taking messages and current state and updating the state or asking the Elm runtime to do things) and subscriptions. Initially this can seem a bit overwhelming but this structure actually keeps things very clear, correct and makes it remarkably easy to aggressively make changes with confidence you won't break everything.

You can split these pieces out into different files or not. In Elm (and functional programming more generally) the particular choice of file structure isn't as important as other contexts (the tooling makes it really easy to move things about later anyway).

A(n) mutations is not a core part of an Elm Html.program.

main : Program Never Model Msg

main =

Html.program

{ init = init

, view = view

, update = update

, subscriptions = subscriptions

}

Often you will start by creating a model capturing the basic details of your problem domain. You can use types and type aliases as already described to unambiguously model the state. The details will vary (and, again, things are easy to change later so you don't need to worry about getting it correct first time) but a few general principles apply which may be contrary to your instincts if you come from an Object Orientated background.

Avoid primitives, particularly booleans. Even if what you are capturing seems ideal for a boolean, at least consider using an Elm type.

type LoadingStatus = Loading | Loaded

allows for much clearer code than using a boolean. But it is also extensible and allows you to coherently group things. For example (assuming a type alias for the Data exists):

type LoadingStatus = NotAsked

| Loading

| Loaded Data

| Error String

For you main state you will typically end up with a type alias. Resist the urge to deeply nest things. The OO programmer's instinct will be to 'encapsulate' things. Type aliases inside type aliases inside ... (you get the idea). This is often bad for at least two reasons. Accessing deeply nested data is tricky as soon as there is some structural variation. But often the boundaries and boxes we create don't quite work anyway. In real applications for any particular task you will often need various pieces of information, so go broad. At least initially create a 'wide' type alias for your state. If you find it does genuinely break down into several almost always distinct pieces, great, you can split it up then.

So having created a model we now want to see what it looks like. Let's look at how to create Html.

HTML (and SVG)

Elm's minimal syntax allows us to express view structures in a simple but safe way. For web views we typically compose a function with a list of attributes and a list of children.

From Model to View

Our view function takes our state as an argument and builds a view with it. We will want a few imports:

import Html exposing (Html, text, p, div)

Now we can build a simple view

view : Model -> Html Msg

view model =

div []

[

p [] [ text "Hello World" ]

]

A few things may seem confusing at first (and you really will want to ensure you have elm-format installed and configured to apply on save when working with views!) but they more or less follow just one simple form. You can think of each Html element as taking two arguments: a list of attributes and a list of children. The exception is text which is just text, that is will get rendered eventually just as text not as a tag or attribute.

There is no special syntax (unlike the JSX you may have seen in React-world), Elm's own syntax is clear and concise enough.

One thing to watch out for is that text (and many other things) expect a string argument so you will need to sprinkle in quite a lot of (toString thing) and do manual building of strings (there is no interpolation yet in Elm):

text "Hi, " ++ model.person.name

++ " you have "

++ (toString model.messages)

++ " messages."

Honestly, these two aspects can be a bit tedious. You can always create functions to encapsulate these (either by mapping stuff to strings or in the above handling details like pluralisation in the 1-message case) but you do have to write the code (sorry). Lack of string interpolation is one of the few areas where Elm is obviously lacking compared to other ecosystems. (I would be nice if this was addressed soon, although Richard Feldman's Github issue on this was opened in March 2015!)

Classes, Styles and Attributes

We pass a list of attributes to each Html primitive first. This includes classes, styles and other attributes.

First let's do the imports:

import Html.Attributes exposing (style, class)

Then apply these basics.

view : Model -> Html Msg

view model =

div [class "container"]

[

p [ style [("color", "blue")]]

[ text "Hello World" ]

]

Most Html attributes take a single string argument. But style takes a list of Tuples, each a key-value pair. But you probably don't want to do too much of this (there are various approaches to styles in Elm or you can just use an external (CSS) approach).

SVG

SVG works exactly the same way as Html. For example

import Svg exposing (svg, circle)

import Svg.Attributes exposing ( viewBox

, points

, fill

, cx

, cy

, r)

svg [ viewBox "0 0 400 400" ]

[ circle

[ fill "#e9e9e9"

, r "20"

, cx (toString x)

, cy (toString y)

]

[]

]

Updates and Messages

We define a type for our updates (our Message type). In our example this is:

type Msg

= TimeUpdate Time

| KeyDown KeyCode

| CellSelection Cell

| StartGame

these are all possible updates to our state and come from a variety of sources. But having a single type for updates and single update function makes everything very clear and easy to debug. Indeed with the Elm tooling we can step through every single update applied to understand what is going on and address any unexpected behaviour.

Let's look at the update function.

update : Msg -> Model -> ( Model, Cmd Msg )

update msg model =

case msg of

TimeUpdate dt ->

( updatePosition dt model

, Cmd.none )

KeyDown keyCode ->

( keyDown keyCode model

, Cmd.none )

CellSelection cell ->

( selectCell cell model

, Cmd.none )

StartGame ->

( restart model

, Cmd.none )

We use a case expression to handle each possible case. The compiler ensures we never forget any and always return a valid next state. For each of our four possible updates we calculate a new state (and in this case don't ask the Elm runtime to do any commands). As you can see each of the actual updates has been broken out into its own function.

As case is an expression we could actually write the above more concisely:

update : Msg -> Model -> ( Model, Cmd Msg )

update msg model =

((case msg of

TimeUpdate dt ->

updatePosition dt model

KeyDown keyCode ->

keyDown keyCode model

CellSelection cell ->

selectCell cell model

StartGame ->

restart model)

, Cmd.none)

although I don't think that makes sense here. It is actually a bit harder to read and if at some point our game submitted a final score to a backend then this would no longer work. But I include it here to show how everything is just functions, you can easily move around and simplify things.

Mouse and Keyboard

To use keyboard and mouse input we use subscriptions. These subscriptions pass updates to our update function ensuring that we explicitly update the state of our application based on the new information.

subscriptions : Model -> Sub Msg

subscriptions model =

Keyboard.downs KeyDown

where KeyDown is our update for keyboard events. We handle this in our update

update : Msg -> Model -> ( Model, Cmd Msg )

update msg model =

case msg of

KeyDown keyCode ->

( keyDown keyCode model, Cmd.none )

and can extract this to a single function to handle the inputs we care about and ignore others. For example

keyDown : KeyCode -> Model -> Model

keyDown keyCode model =

case keyCode of

38 ->

(selectCell Top model)

40 ->

(selectCell Bottom model)

37 ->

(selectCell Left model)

39 ->

(selectCell Right model)

27 ->

{ model

| state = Start

}

32 ->

restart model

_ ->

model

Notice how we have a catch all case. In Elm we must handle all possible branches in a case expression. _ will match everything (so would k or any other binding, we use _ to signify that we don't care about the actual value).

We have left out quite a few details of the App, however we have covered all the key pieces. There are just a few core concepts: state, update messages, updates and subscriptions. And everything is functions. There really isn't much more to it (although some parts will seem weird coming from Java(script)).

Lists

We create lists in Elm with square brackets. All items must be of the same type.

> [1,2,3]

[1,2,3] : List number

> ["a", "b", "c"]

["a","b","c"] : List String

A list actually consists of a first element (head) and rest of list (tail). We use :: to 'glue' these together.

> 1 :: []

[1] : List number

> 1 :: [2]

[1,2] : List number

> 1 :: 2 :: 3 :: []

[1,2,3] : List number

In most functional programming languages lists are the most common data structure. We can naturally write recursive algorithms which operate on the head and tail. For example:

> f x = case x of

h :: t -> h + (f t)

[] -> 0

<function> : List number -> number

> f [1,2,3]

6 : number

However, in Elm we will more commonly use standard list functions. There are a few basic functions. The easy ones are isEmpty and length which do the obvious things.

isEmpty : List a -> Bool

length : List a -> Int

We can also reverse a list or find out if a particular item is in a list.

reverse : List a -> List a

member : a -> List a -> Bool

We can extract the head or tail (a list can be thought of as head :: tail) with

head : List a -> Maybe a

tail : List a -> Maybe (List a)

Notice how these are Maybe a as an empty list has neither head nor tail. However a one item list does have a tail, the empty list!

The simplest ways to extract items from a list are:

take : Int -> List a -> List a

drop : Int -> List a -> List a

filter : (a -> Bool) -> List a -> List a

take takes up to the number of items supplied from a list. drop drops the supplied number of items (where possible). Where filter returns a list with only items which return True from the supplied function.

> List.take 2 [1,2,3]

[1,2] : List number

> List.take 4 [1,2,3]

[1,2,3] : List number

> List.drop 2 [1,2,3]

[3] : List number

> List.filter (\n -> n > 1) [1,2,3]

[2,3] : List number

A very common list function (for example in generating labels for a data visualisation or indicies for some kind of processing is range). It generates a list of integers between and including the two values supplied. For example.

> List.range 1 5

[1,2,3,4,5] : List Int

> List.range 1 10

[1,2,3,4,5,6,7,8,9,10] : List Int

But if that range is empty an empty list is returned.

> List.range 1 -4

[] : List Int

We can compose lists with append and concat. Append joins two lists together. Whereas concat takes a list of lists and appends them all together into one list.

> List.append [1,2] [3,4]

[1,2,3,4] : List number

> List.concat [[1,2], [3,4], [5,6]]

[1,2,3,4,5,6] : List number

To do simple processing of a list we use map which applies a function to each element.

map : (a -> b) -> List a -> List b

> List.map (\n -> n * n) [1,2,3]

[1,4,9] : List number

> List.map String.toUpper ["hi", "world"]

["HI","WORLD"] : List String

There are actually versions of map for functions operating on multiple lists. For example map2.

> List.map2 (*) [1,2,3] [4,5,6]

[4,10,18] : List number

Here we wrap the infix operator in (*) to pass as a regular function.

There are some standard functions which work on entire lists.

sum : List number -> number

product : List number -> number

maximum : List comparable -> Maybe comparable

minimum : List comparable -> Maybe comparable

These all do the obvious thing (though it is not obvious that these functions should be in the List module). There are also two functions for looking at logical queries on a list all and any: each takes a function that maps each element to True or False. Then if all (any) hold true for all (any) it returns True, otherwise False.

all : (a -> Bool) -> List a -> Bool

any : (a -> Bool) -> List a -> Bool

We can do custom operations on entire lists with foldl, foldr and scanl. foldl is commonly called reduce in other languages. These take a function which takes a list item, the accumulated value and returns a new accumulated value; an initial accumulated value; and a list. The function then returns the final accumulated value having 'folded' across the entire list. That sounds really complex but let's look at some simple examples.

> List.foldl (+) 0 [1,2,3,4]

10 : number

> List.foldl (*) 1 [2,3,4]

24 : number

foldr just does the same but 'folds' from the right (i.e. starts from the end of the list). scanl instead of returning the final accumulated value, returns all the values as we pass through the list.

> List.foldr (+) 0 [1,2,3,4]

10 : number

> List.scanl (*) 1 [2,3,4]

[1,2,6,24] : List number

There are several advanced maps. We look at two here: filterMap and indexedMap. The first applies a function to a list which returns a Maybe. It then drops any Nothings (perhaps resulting in an empty list). This can make data processing pipelines a lot more concise.

filterMap : (a -> Maybe b) -> List a -> List b

queries

|> filterMap getResultIfAny

|> map convertResult

indexedMap allows you to write functions to apply to list elements along with their index (for example when drawing a data visualisation we may want to offset each item by their index (place in list) scaled).

indexedMap : (Int -> a -> b) -> List a -> List b

indexedMap (\idx d -> { x = (toFloat idx) * dx

, y = y * dy

}

)

data

We have three standard ways to sort a list. The first works in the 'standard' way with standard Elm primitives which are comparable (numbers, characters, strings, lists of comparables and tuples of comparables).

sort : List comparable -> List comparable

> List.sort [2,4,1,2,6,9]

[1,2,2,4,6,9] : List number

We can also sort by something extracted from each list item with sortBy. If these items are records we can use .key to pull out the key item from each.

sortBy : (a -> comparable) -> List a -> List a

> sortBy .key listOfRecordsWithKey

If we want full control of sort we do this with sortWith and the helper Order type. Here we write a function that takes two list elements and then returns an Order, either: LT, EQ and GT (less than, equal, greater than). This is similar to javascript where you might pass a sort function which returns a negative number, zero or a positive number to indicate how two elements are related.

sortWith : (a -> a -> Order) -> List a -> List a

sortFrom is not a core List sorting function.

There are a handful of other List functions. Two of note are partition which generates two lists, one which satisfies a condition, the other which fails and unzip which takes a list of a Tuple and converts it into a Tuple of lists of each item.

partition : (a -> Bool) -> List a -> (List a, List a)

unzip : List (a, b) -> (List a, List b)

Array

If you program in most standard languages the array, not the list, is the most common data structure. Arrays allow for fast arbitrary access or insertion of elements. Elm has Arrays and while they lack both the simplicity and some of the collection level operations of lists, they are ideal when you want to be able to access or modify by location. Like List, Elm Arrays contain items of a single type.

We create Arrays with one of the following standard functions.

empty : Array a

> Array.empty

Array.fromList [] : Array.Array a

repeat : Int -> a -> Array a

> Array.repeat 3 "hi"

Array.fromList ["hi","hi","hi"] : Array.Array String

fromList : List a -> Array a

> Array.fromList [1,2,3]

Array.fromList [1,2,3] : Array.Array number

We can also do a fancy (per index) initialisation with:

initialize : Int -> (Int -> a) -> Array a

> Array.initialize 3 (\n -> n + 4)

Array.fromList [4,5,6] : Array.Array Int

Having initialised an Array the basic operations of getting an item or adding an item (actually we don't really add an item we produce a new Array with that item included) are via the following functions.

get : Int -> Array a -> Maybe a

set : Int -> a -> Array a -> Array a

Let's see these in action.

> a = Array.fromList [1,2,3]

Array.fromList [1,2,3] : Array.Array number

> Array.get 2 a

Just 3 : Maybe.Maybe number

> Array.get 6 a

Nothing : Maybe.Maybe number

> Array.set 2 1 a

Array.fromList [1,2,1] : Array.Array number

> Array.set 12 1 a

Array.fromList [1,2,3] : Array.Array number

> a

Array.fromList [1,2,3] : Array.Array number

Notice how invalid indexes (out of range) are ignored on setting. And that the original array (here bound to a) is never altered.

We have fewer functions on Arrays than Lists but there are similar basics:

isEmpty : Array a -> Bool

length : Array a -> Int

push : a -> Array a -> Array a

append : Array a -> Array a -> Array a

These are largely as for List, though push is new. It just adds to the end of an Array.

> Array.push 4 a

Array.fromList [1,2,3,4] : Array.Array number

We have several collection level operations which function similarly to those available for List.

map : (a -> b) -> Array a -> Array b

indexedMap : (Int -> a -> b) -> Array a -> Array b

filter : (a -> Bool) -> Array a -> Array a

foldl : (a -> b -> b) -> b -> Array a -> b

foldr : (a -> b -> b) -> b -> Array a -> b

And you can convert to a List with

toList : Array a -> List a

Or toIndexedList if you want indices (it produces a List (Int, a) where the first item of each Tuple is the index).

Perhaps the most interesting 'new' function is slice which allows us to extract part of an Array from a start up to but not including an end index. Let's see a few examples.

> Array.slice 0 2 a

Array.fromList [1,2] : Array.Array number

> Array.slice 1 3 a

Array.fromList [2,3] : Array.Array number

> Array.slice 1 21 a

Array.fromList [2,3] : Array.Array number

Again Elm just does its best to return what you ask for, but if you specify too broad a range you will get whatever is available. You can actually use negative indices to offset from the end of the array.

PS Elm never actually modifies your Array, as it is immutable, but provides functions that return a new Array with a change applied.

Dicts

A Dictionary allows us to flexibly store items by a key and is available in Elm from the Dict module.

A Dict is a key value store. The keys must be comparables i.e. Int, Float, Char, String and tuples (of comparables) or lists (of comparables). The keys and values must be the same types for a given Dict, that is

type Dict k v

We can build a Dict either starting without keys or values (with empty) or from a list of key, value pairs (in Tuples).

fromList : List (comparable, v) -> Dict comparable v

In elm-repl these look like

> Dict.empty

Dict.fromList [] : Dict.Dict k v

> Dict.fromList [("a",1), ("b",2)]

Dict.fromList [("a",1),("b",2)]

: Dict.Dict String number

The representation of the Dict above doesn't make things very clear but you could think of them as being roughly the same as the JSON {} and {"a": 1, "b": 2}.

Having created a Dict we can modify with three key functions (again we don't really modify but create a new Dict as the result of each function).

insert

:  comparable

-> v

-> Dict comparable v

-> Dict comparable v

update

:  comparable

-> (Maybe v -> Maybe v)

-> Dict comparable v

-> Dict comparable v

remove

: comparable

-> Dict comparable v

-> Dict comparable v

In action these look like

> d = Dict.fromList [("a",1), ("b",2)]

Dict.fromList [("a",1),("b",2)]

: Dict.Dict String number

> Dict.insert "c" 3 d

Dict.fromList [("a",1),("b",2),("c",3)]

: Dict.Dict String number

> Dict.update "b" (\_ -> Just 4) d

Dict.fromList [("a",1),("b",4)]

: Dict.Dict String number

update is the complicated one: it takes a function which takes the a Maybe of the existing value (that is Nothing if it doesn't exist or Just value if it does) then returns a replacement value (or Nothing if it should be removed).

> Dict.update "b"

(\v ->

(Maybe.map

(\u -> u + 10)

v

)

)

d

Dict.fromList [("a",1),("b",12)] :

Dict.Dict String number

We can get back to the simpler, friendlier world of lists in 3 ways (all do the obvious thing).

keys : Dict comparable v -> List comparable

values : Dict comparable v -> List v

toList : Dict comparable v ->

List (comparable, v)

To do operations on Dicts we have similar things available to List and Array. map operates on values (it creates a new Dict with the same keys).

map

:  (comparable -> a -> b)

-> Dict comparable a

-> Dict comparable b

foldl and foldr operate on sorted keys. The key argument is a function on key, value and accumulated value which returns the new accumulated value. If you want to ignore the keys (which is probably typical) you might want to use Dict.values to pull out the values and then just use List.foldl/r as that will probably be cleaner.

foldl

:  (comparable -> v -> b -> b)

-> b

-> Dict comparable v

-> b

foldr

:  (comparable -> v -> b -> b)

-> b

-> Dict comparable v

-> b

filter again works with both key and value and produces a new Dict with those items satisfying your predicate.

filter

:  (comparable -> v -> Bool)

-> Dict comparable v

-> Dict comparable v

And partition takes similar arguments, but produces two Dicts, one satisfying the predicate, one failing it.

partition

:  (comparable -> v -> Bool)

-> Dict comparable v

-> (Dict comparable v, Dict comparable v)

Composing Dicts is more interesting than for List or Array. union joins two Dict (and if keys collide then we keep the value from the first Dict), intersect keeps a key-value from the first Dict when its key appears in the second and diff keeps a key-value pair from the first when it does not appear in the second. There is also merge which is the most general combination function but it is very complex as it gets you to handle all three cases (key in first only, key in second only, key in both).

The other core data structure not included here is Set which allows you to track sets of unique values. It is roughly like having a Dict where the keys are the same as the values.

Strings in Elm

In Elm we use the String module for string related functions. We can do all of the basic standard string operations in a type safe way. But if you are used to Javascript's limited String API you are in for a treat. Elm has a comprehensive, pragmatic set of string operations out of the box.

> String.isEmpty ""

True : Bool

> String.isEmpty "Hi"

False : Bool

> String.length "Hi"

2 : Int

> String.length "Hello Elm"

9 : Int

> String.reverse "Hello Elm"

"mlE olleH" : String

> String.repeat 5  "Hello Elm"

"Hello ElmHello ElmHello ElmHello ElmHello Elm" : String

> String.toUpper "Hello Elm"

"HELLO ELM" : String

> String.toLower "Hello Elm"

"hello elm" : String

But we can do lots of pragmatic, App-relevant functions including stuff that would be in a 3rd party library in Javascript.

> String.trim "   Hello Elm    "

"Hello Elm" : String

> String.padLeft 5 '*'  "Hello Elm"

"Hello Elm" : String

> String.padLeft 20 '_'  "Hello Elm"

"___________Hello Elm" : String

> String.padRight 20 '_'  "Hello Elm"

"Hello Elm___________" : String

> String.pad 20 ' '  "Hello Elm"

"      Hello Elm     " : String

We can flexibly extract sub strings.

> String.slice 6 5 "Hello Elm"

"" : String

> String.slice 6 9 "Hello Elm"

"Elm" : String

> String.left 5 "Hello Elm"

"Hello" : String

> String.right 3 "Hello Elm"

"Elm" : String

> String.dropRight 4 "Hello Elm"

"Hello" : String

or check conditions on substrings.

> String.contains "Elm" "Hello Elm"

True : Bool

> String.startsWith "Elm" "Hello Elm"

False : Bool

> String.endsWith "Elm" "Hello Elm"

True : Bool

> String.indexes "l" "Hello Elm"

[2,3,7] : List Int

And we can nicely compose or decompose Strings.

> String.join ", " ["a", "b", "c"]

"a, b, c" : String

> String.split "," "a,b,c"

["a","b","c"] : List String

> String.words "How are you"

["How","are","you"] : List String

We can also do higher order functional operations on strings, performed on characters with map, filter, any and so on. Although in practice you probably won't use these features too much. Although filter : (Char -> Bool) -> String -> String is pretty useful for clearing out invalid characters e.g.

normalisePhoneNumberString numberString

= String.filter Char.isDigit numberString

> normalisePhoneNumberString "Elm123 @#"

"123"

Characters in Elm

The Char module is fairly tiny but useful in association with (from String)

toList : String -> List Char

fromList : List Char -> String

and standard list progressing functions.

We can classify with:

isUpper : Char -> Bool

isLower : Char -> Bool

isDigit : Char -> Bool

isHexDigit : Char -> Bool

which may be useful for simple validations.

We can convert:

toUpper : Char -> Char

toLower : Char -> Char

And with locale specific mappings:

toLocaleUpper : Char -> Char

toLocaleLower : Char -> Char

We can also convert to and from keyboard key codes (this might actually be the most common use of this module):

type alias KeyCode = Int

toCode : Char -> KeyCode

fromCode : KeyCode -> Char

We have already seen these keycodes together with a case expression in the Mouse and Keyboard chapter.

Regular Expressions

For more elaborate String validations and matching we can use regular expressions. Elm provides a nice set of functions for working with regular expressions in its core library.

We construct regular expressions (type Regex) with regex which takes a string. Notice the double \\ this is so that a backslash is included, rather than the next letter being escaped.

> regex "0123"

> regex "Elm"

> regex "\\d{1,3}"

There are four basic functions to work with Regex: contains, find, replace and split.

contains does what you might expect

contains : Regex -> String -> Bool

> contains (regex "\\d") "1234"

True

> contains (regex "\\D") "1234"

False

> contains (regex "[a-c]") "1234"

False

> contains (regex "[a-c]") "12a23"

True

Before we look at the other main functions we need to learn about the data structures Elm provides to make working with them pleasant. To specify the number of (potential) matches we have the elegant and precise type:

type HowMany

= All

| AtMost Int

And when we get matches the type used is:

type alias Match =

{ match : String

, submatches : List (Maybe String)

, index : Int

, number : Int

}

Where the match is the match, submatches and parenthetical capture groups (i.e. parts of match grouped in brackets), the index is original location and the number is the index of the match itself.

Actually once you understand these types there is very little left to worry about (unlike other languages where you often seem to have to look up weird details of what regular expression methods return).

find : HowMany

-> Regex

-> String

-> List Match

findThreeNumbers = find (AtMost 3) (regex "\\d")

As find returns a list of Match you can pull out the matches or submatches or perform further processing very straightforwardly.

Let's consider replace. Again the function signature tells you pretty much all you need to know.

replace:  HowMany

-> Regex

-> (Match -> String)

-> String

-> String

The only non obvious part is the Match -> String i.e. you can actually write a function from the match to the replacement. This gives you access to all the Match fields, so you can do whatever you want.

In many cases this function might produce the same output. To signal to the Elm compiler this is intentional use an _.

removeUpToTwoNumbers = replace

(AtMost 2)

(regex "\\d")

(\_ -> "")

shortenWords = replace

All

(regex "\\w+")

(\{match} -> String.left 3 match)

The final function is split

split : HowMany

-> Regex

-> String

-> List String

> split All (regex ",") "1,2,3"

["1","2","3"]

You might want to put these back together again (after some processing or filtering). For that use

join : String -> List String -> String

Debugging

From Javascript you may be used to console logs all over the place (or the use of break points). In Elm, thanks to the helpful compiler we usually don't need this kind of thing. But we can Debug in a simple, clean way with the Debug module.

For logging we can use Debug.log which has signature:

log : String -> a -> a

Basically it takes a label and an argument and returns that argument. But it also logs the argument with the label provided. Let's see it in action:

> f x = 2 * Debug.log "f called with argument" x

<function> : number -> number

> f 3

f called with argument: 3

6 : number

> f -3

f called with argument: -3

-6 : number

In development we might want a simple clean way to crash a program (to identify something we haven't yet implemented). Debug.crash does this.

> f x = case x of\

|    True -> "done"\

|    _ -> Debug.crash "TODO: implement"

<function> : Bool -> String

> f True

"done" : String

> f False

Error: Ran into a `Debug.crash` in module

`Repl`

This was caused by the `case` expression

between lines 4 and 6. One of the branches

ended with a crash and the following value

got through:

False

The message provided by the code author is:

TODO: implement

NB This should not be used for control flow or indeed I can't think of any case where this should make it into real (released) App code.

Randomness

When we write Elm we write pure functions, so we expect no side effects and the same output for the same input. So how do we get random values if we want to role a dice or place a new enemy in computer game? We use the Random module.

Let's look at a simple example before trying to understand more broadly how this works.

Let's assume we are creating a board game designed to teach children about the evils of capitalism. We need to roll a dice and let's say whenever you roll a 6 the bubble bursts and you lose everything. Let's create a very simple model.

type alias GameModel =

{ lastDiceRoll : Maybe Int

, dollars: Int

}

type Msg = Roll | NewRoll Int

And let's throw together a simple view.

view : Model -> Html Msg

view model =

div []

[ h1 []

[ text (toString model.lastDiceRoll) ]

, button

[ onClick Roll ]

[ text "Roll again" ]

]

and initial model (with "a small loan of a million dollars" to get things started):

init : (GameModel, Cmd Msg)

init =

(GameModel Nothing 1000000, Cmd.none)

And an update function (here is the interesting bit).

type Msg = NewRoll | Roll

update : Msg

-> GameModel

-> (GameModel, Cmd Msg)

update msg model =

case msg of

Roll ->

(model

, Random.generate

NewRoll

(Random.int 1 6)

)

NewRoll roll ->

case roll of

6 -> ( { model | lastDiceRoll = roll

, dollars = 0

}

, Cmd.none

)

_ -> ( { model | lastDiceRoll = roll

, dollars = model.dollars + roll

}

, Cmd.none)

So the key bit is Random.generate NewRoll (Random.int 1 6), but what is that doing? It is a command, essentially asking if a random number between 1 and 6 can be generated by the Elm runtime and when generated an update of NewRoll will be applied. We don't do any dirty random number generation in our Elm code: it is handled for us and the result returned in a clean update.

The generate function has signature: generate : (a -> msg) -> Generator a -> Cmd msg. So it takes an update function (here NewRoll) and a specification of how a random number should be generated. We then get back a command to be performed.

Okay what if you want something more complex, for example two dice. First let's encapsulate the dice roll specification in a function:

diceRoll : Generator Int

diceRoll = Random.int 1 6

And update our updates to include a DoubleRoll and DoubleRollResult which takes tuple of two Ints.

So now we can perform it with

case msg of

DoubleRoll ->

( model

, Random.generate

DoubleRollResult

Random.pair

diceRoll

diceRoll

)

As pair : Generator a -> Generator b -> Generator (a, b) takes two specifications and combines them to a single specification which Random.generate will request is performed.

You can do much more advanced things with random numbers but typically you will only want to generate a single number so things are quite simple. Elm makes this kind of process which can result in behaviour which is incredibly hard to debug in other languages extremely clean and clear.

Time

When we work with time in Elm using Time is recommended. We skipped over this in the App we looked at in before but there we actually subscribed to request animation frame updates (basically a performant way to get browsers to let us know when we should update animated parts of a web page).

The subscriptions were (in full):

subscriptions : Model -> Sub Msg

subscriptions model =

Sub.batch

[ AnimationFrame.diffs TimeUpdate

, Keyboard.downs KeyDown

]

And the TimeUpdate must be an update which accepts a single Time. But what is Time. It is basically a nice way to work with Time that means we don't get confused about units.

When working with time we have 4 units:

millisecond : Time

second : Time

minute : Time

hour : Time

and four conversions:

inMilliseconds : Time -> Float

inSeconds : Time -> Float

inMinutes : Time -> Float

inHours : Time -> Float

I don't think I actually need to explain any of these! But they should be used instead of the raw Time values.

An alternative subscription (for less frequent events, or where performance is not a concern) is every

every : Time -> (Time -> msg) -> Sub msg

type Msg = ShouldPollServer Time

every minute ShouldPollServer

This (as it suggests updates us regularly with the interval specified)

Dates

Elm has a simple core Date module. We can create Dates in a number of ways.

One (which we won't focus on here) is to ask the Elm runtime via a Task. The others are:

fromString : String -> Result String Date

Elm forces you to handle the failure case.

fromTime : Time -> Date

You can convert a Time to a date as it is interpreted as the milliseconds since the Unix epoch (you may be familiar with this from other languages). You can also go in the other direction.

toTime : Date -> Time

You can extract all the standard things from a date. Elm has types for months and days of the week but otherwise Ints are used (they have a very natural meaning for the other items). In full these are:

year : Date -> Int

month : Date -> Month

day : Date -> Int

dayOfWeek : Date -> Day

hour : Date -> Int

minute : Date -> Int

second : Date -> Int

millisecond : Date -> Int

There isn't much to say about these, and while limited this basic functionality will be fine for many simple cases.

Ports

Elm provides a wonderfully safe and productive world in which to build and reshape Web Apps. However, sometimes we need to do something not supported by Elm or interoperate with existing Javascript code. For this there are ports. These ensure that dirty and dangerous Javascript can't mess with our Elm paradise. They provide a simple but safe and clearly structured way to communicate between Elm world and Javascript world. You can think of it as like sending messages between two isolated processes or like a client and server.

From Elm to Javascript

We must declare a module as being a port module to enable the extra functionality.

port module Example.Ports exposing (..)

port sayHiToJS : { message : String }

-> Cmd msg

Here we declare (we don't define the details) a mapping from a record (we can use standard Elm primatives and combinations thereof too) to a command.

To actually send a message to Javascript we should (in a branch of our update function send this command), so something like:

case update of

[...]

SayHi message

-> (model, sayHiToJS { message = message })

In Javascript we can subscribe sayHiToJS with something like (we show the React/Webpack integration in a later chapter, note we are using ES6 features like destructuring and arrow functions here):

app.ports.sayHiToJS.subscribe(

({ message }) => console.log(message)

)

`

That is actually it. We need to explicitly subscribe to each port in Javascript.

From Javascript to Elm

Now let's go in the other direction. To send data from Javascript we just do something like.

app.ports.sayHiToElm.send({ message: "Hi Elm!" })

But that won't work unless we have set up the port in Elm to listen. In our Example.Ports module we need to add:

port sayHiToElm :

({ message : String } -> msg) -> Sub msg

We should also add to our subscriptions for the Elm App something like

sayHiToElm MessageFromJS

where MessageFromJS is an update type like

type Updates =

[...]

| MessageFromJS { message: String }

Then we just handle this update like any other in our update function

case update of

[...]

MessageFromJS { message }

-> ({ model | jsMessage = message }, Cmd.none)

If we try sending any invalid data into Elm it will get turned around at the border, along with a helpful error message. While ports may seem like a lot of hassle to set up they ensure that data coming into Elm is safe. They are also very easy to update once in place as the compiler (and at runtime the ports system) will help you out a lot.

Using Elm within React (and Webpack)

Elm is really easy to embed as a React component with react-elm-components. It is well documented in its readme has three key parts: the Elm source, flags to pass and ports. Assuming you are already using Webpack it is very simple.

Webpack

You will want to set up the Elm Webpack Loader. See the website for latest instructions. Crucially you will need to add to your webpack configuration. In Webpack 2 this looks something like:

module.exports = {

module: {

rules: [{

test: /\.elm$/,

exclude: [/elm-stuff/, /node_modules/],

use: {

loader: 'elm-webpack-loader',

options: {}

}

}]

}

};

This can be a bit tedious to set up but once done offers a great development experience, which is compatible with things like live reloading and in browser error reporting. You can also use the great Elm debugging tools (full timeline) from Elm 0.18. Okay so now we've set up Webpack let's get our Elm code exposed as a standard React component.

Elm source (embedding Elm)

We embed in a very straightforward way. This might be all you need in a pure Elm app. Although as you are embedding you probably want to pass something in/around.

import Elm from 'react-elm-components'

const { ElmApp } = require("./elm/ElmApp.elm");

export default () => <Elm src={ElmApp} />

Flags

You can add flags (NB you need to use the right kind of Elm program)

import Elm from 'react-elm-components'

const { ElmApp } = require("./elm/ElmApp.elm");

export default () => <Elm

src={ElmApp}

flags={{admin: true}}

/>

Ports

You can also set up ports by passing in a function to do this. It uses send to sent in to a port or subscribe to listen for messages from a port.

import Elm from 'react-elm-components'

const { ElmApp } = require("./elm/ElmApp.elm");

const setupPorts = (ports) => {

ports.requestData.subscribe(function(n) {

// Make call/retrieve

ports.receiveData.send("Some data");

});

};

export default () => <Elm

src={ElmApp}

ports={setupPorts}

/>

JSON Decoding

On one hand Elm is build for a future where we no longer use terrible(!) formats like JSON. But the reality of the web today (even emerging standards like GraphQL) is that JSON plays a central role. It has become the standard way to offer an API to users of all kinds of your data. But JSON is not only built for Javascript, it is quite literally built in Javascript; it (more or less) is the basic primitives of Javascript as a data format. It is flexible, un-typed and messy; exactly what Elm is not. How do we transfer information from this world into Elm? We use decoders.

Unfortunately your first experience Elm's JSON decoding is unlikely to be pleasant. You will probably wonder what on earth is going on, find it frustrating and perhaps even give up on Elm completely. The good news is that once you have figured out one or two examples you will more or less get it. But that understanding can be hard to convey to another person, as the approach makes use of function composition in a way that until you have done quite a lot of functional programming will feel foreign.

The basics

Typically we use decodeString which takes a Decoder (a thing which decodes a JSON string) and a string (of JSON). The result will either be an Err or an Ok of the type that the decoder decodes. The Json.Decode module comes with basic decoders such as string (NB this is a decoder of strings).

> decodeString string "\"Hello World\""

Ok "Hello World"

We then combine these primitive decoders with more complex kinds: those relating to data structures and those on objects. An list is the simplest case:

> decodeString (list int) "[1,2,3,4]"

Ok [1,2,3,4]

> decodeString (list bool) "[false,true,false]"

Ok [False,True,False]

We can decode objects (in JSON) in two ways. The first is is via decoding to Dict

> decodeString (dict int) "{ \"elm\": 0, \"book\": 1 }"

Ok (Dict.fromList [("book",1),("elm",0)])

But more commonly with structured data we will want to pull out specific key/values from an object. Let's say we have the following JSON:

{

"title": "Elm Accelerated",

"meta": {

"pages": 450,

"author": "James Porter"

}

}

We can created a decoder to convert this to an Elm type as follows:

> type alias Book = { title : String, pages : Int }

> bookDecoder =\

map2 Book\

(field "title" string)\

(at ["meta", "pages"] int)

> decodeString bookDecoder "{\"title\": \"Elm\", \"meta\": { \"pages\": 450}}"

Ok { title = "Elm", pages = 450 }

We are extracting a couple of fields, one of which was nested. But now that you have seen the basics, lets look at how you will really do it.

Examples

I suggest you use the json-to-elm project to look at various examples. Typically in real world projects you will have to make some customisations or simplifications but the Elm compiler will be there to help you out. If you are in control of your API you may actually want to try to make it quite flat and regular to make it easier to get it into Elm (but that is good advice in general: deeply nested variably structured data is going to be difficult to deal with using any client).

You should have a look at your own examples. Start simple and add more complexity. Or paste in JSON from real APIs to get a feel for how it works.

Strengths

If the JSON ever ends up malformatted the errors will be excellent, which can often be very helpful if you are creating the data in a language like Javascript or Ruby where there is little help for structural issues. The other strength which is not obvious, is that when you come to add a new field to your data it is really easy to add it to the decoder and all other parts of your Elm code. The compiler will helpfully point out all the changes you need to make.

(Overcoming the) Weaknesses

If the data is really difficult to deal with (and perhaps you aren't in control of it) you might actually want to process it in Javascript where you can take hacky shortcuts, not worry about types and so on; but then map it into a nice, regular format which you could even pass directly into Elm via a port. I've found this works pretty well. It might also be a good option if you have a lot of existing Javascript code around for example how the API authenticates: rather than re-implementing it all in Elm you can just to the messy stuff in Javascript.

Onward

Like all learning resources this is incomplete. We have covered most of the core Elm library and core concepts. We left off Bitwise, Color and Json.Encode entirely but you will find these pretty easy to pick up if you need them (you may never need the first two). However, the key next step is to build substantial real projects with Elm. In its niche (fairly complex, interactive Web Apps) you'll discover that Elm can be a fantastic toolkit, better than anything else out there. Go build things.

Table of Contents

- Table Of Contents -

Introduction

The Elm Architecture Part 1

The Compiler as Assistant

Functions

if then else, case

let

Lists

Comments

elm-repl

elm-package

elm-reactor

elm-make (and Webpack)

elm-format

elm-live

elm-test

Types

Tuples

Records

Type Aliases

(Union) Types

How to learn Elm

The Elm Architecture Part 2

HTML (and SVG)

Mouse and Keyboard

Lists

Array

Dicts

Strings in Elm

Characters in Elm

Regular Expressions

Debugging

Randomness

Time

Dates

Ports

Using Elm within React (and Webpack)

JSON Decoding

Onward