In Day 2, we’re going to build the skills necessary to attack the most sophisticated of user interface problems: building a game. We’re going to learn to handle user input and output, the most difficult concepts for functional languages. We’ll also learn to display images. You’ll find that Elm is a natural language for doing so.
As a wanna-be browser language, Elm has a big disadvantage. It’s not JavaScript. You’ll need to rely on another layer in the browser to compile Elm to JavaScript. But Elm also has a huge advantage.
It’s not JavaScript.
If you want to herd sheep like a sheep dog, you don’t necessarily have to be a dog. You just have to herd sheep.
Before we get rolling, let’s spend some time with Evan Czaplicki, creator of Elm. He’ll help us understand the motivations behind the language.
Why did you create Elm?
I was extremely frustrated by HTML and CSS. Basic things like centering, or even worse vertical centering, were shockingly difficult. I kept finding five ways to do the same thing, each with its own set of weaknesses and corner cases. I wanted reusable styles and components. I was going to use the same sidebar on every page and there just was not a way. It makes sense why these things were hard in a language originally designed for text markup, but I felt that there had to be a more declarative and more pleasant way. So my goal was to create a better way to do GUI programming. I wanted to write front-end code that I was proud of.
So why choose a functional language?
I wanted to show that functional programming can be great for real problems. Many functional folks have a way of saying extremely interesting and useful things in a totally inaccessible and impractical way, and I wanted to fix this. I wanted to prove that functional programming actually helps you write nicer code. Elm’s focus on examples, quick visual feedback, and shockingly short code are all meant to prove that purely functional GUIs are a good idea.
What were your main influences?
Haskell has been a big influence, but so have OCaml, SML, and F#. Syntax is very much like Haskell, though semantics are often closer to OCaml. I tend to say “Elm is an ML-family language” to get at the shared heritage of all these languages.
Stephen Chong and Greg Morrisett are my major influences in how I think about programming languages. With that foundation, I try to do a literature review for any new feature and end up looking at all sorts of languages. For example, Java and Python were extremely helpful for Elm’s docs format, and Clojure and Scala are great resources on how to present a compile-to-VM language to people new to functional programming. The full list is quite long by now!
What is the philosophy of the language?
Balance simplicity and expressiveness. Introduce only the minimal set of features to make GUI programming a great experience. Static types, functional programming, and reactive programming are extremely important tools for writing short and reliable code, but it is a lot to learn all at once.
Not only does Elm need to make these things simple and accessible, it needs to make their value immediately obvious. Elm is not about being theoretically better, it is about being demonstrably better.
What is your favorite language feature?
I really love Elm’s extensible records. This feature is based on Daan Leijen’s ideas from Extensible Records with Scoped Labels, and because I was not involved in the theory work, it is something that delights me by balancing expressiveness and simplicity so beautifully. This is the kind of balance I hope to achieve when I design features.
Elm was built from the ground up to handle the most difficult aspects of user interface development. As you work through Day 2, look for ways that this new language helps you herd all of the elements of a great design into a coherent application.
Whether you’re building a business application with user interface controls or a game, you need to be able to react to events. In fact, everything you do is a reaction to some event. The typical JavaScript program relies on sending events through callback functions, making programs much more responsive but at a cost. They’re much too hard to read. Here’s a typical example using the JQuery library with JavaScript that lets you grab the mouse position:
| $(document).ready(function () { |
| var position = {'x': 0, 'y': 0}; |
| $(document).bind('mousemove', function(event) { |
| position = {'x': event.pageX, 'y': event.pageY}; |
| }); |
| |
| setInterval(function () { |
| // custom position code |
| }, seconds * 1000); |
| }); |
Understanding that code takes a little experience. When the page loads, we get a ready callback. At that time, we bind the mousemove event to a function that sets a position variable. Then, at specific intervals, we have another callback function that uses position. Notice that our code binds anonymous functions to events. Said another way, we’re putting JavaScript in charge of the code’s organization. We call this inside-out programming strategy inversion of control.
For a feature so trivial, that code is much too complex, but it’s a trade-off. We get better responsiveness since this program will change the mouse position every time the user moves the mouse. We trade away simplicity. The problem is that we really need both.
In Elm, we don’t give up simplicity to get responsiveness. Instead of inversion of control, we’ll use signals. A signal represents I/O as a value that varies over time. Let’s try it out.
These programs will allow us to see how Elm handles user interaction without callbacks. For this part of the chapter, we’ll use the Elm online editor[59] to try interactive programs without having to fire up your own server. You’ll type code on the left, and see the results on the right. Let’s start with a simple function to pick up the user’s mouse position:
| import Graphics.Element exposing (..) |
| import Mouse |
| |
| main = Signal.map show Mouse.position |
The exposing directive lets us show without specifying the module. Next, click the compile button. You’ll see output that looks like this:
| (29, 162) |
That’s much simpler. We import the Graphics.Element and Mouse modules, and then declare the main function.
Conceptually, the Mouse.position signal represents the values of x and y that vary over time. Signal.map applies a signal to a single function.
In the previous code, the Mouse.position signal represents a tuple containing the mouse position over time. Our function is show, which converts to text. Mouse.position will “fire” whenever the mouse moves, and Signal.map will reevaluate show with the new mouse position. Interestingly, the result is a new signal! Rather than a callback, you have straight composition of functions. The result is revolutionary.
Looking at the bottom of the window, you can see that main is actually a signal—one that we display on the screen. That means Elm will update the window whenever the mouse position moves.
There are no callbacks, and no inversion of control. We just use a signal, convert to text, and map the present value when the signal changes. Let’s try another one.
Let’s use these same principles to produce an interactive experience. We’ll add a couple of functions to count the number of mouse interactions. In functional languages like Elm, you have to learn tricks to handle state. We’ve seen how signals can help access things like the mouse position that changes over time, and how we use recursion to process lists. We manage state by the way we structure our functions. The fold functions, which you might know from Lisp or Haskell, are a good example. They take a two-argument function, an initial value, and a list. Here’s an example of foldl in Elm:
| > foldl (+) 0 [1, 2, 3] |
| 6 : number |
Here’s what happens at each step:
fold (+) 0 [1, 2, 3]. fold takes the initial value of the list, 1, and the accumulator, 0, and adds them together, returning 1, and uses that number, with the remainder of the list, calling fold again.
fold (+) 1 [2, 3]. Elm takes the leftmost value of the list, 2, and the accumulator, 1, and passes those to the (+) function, returning 3.
fold (+) 3 [3]. We call (+) with the accumulator 3 and the leftmost list element of 3, returning 6, and we’re done.
Now, we create one signal with foldp. That signal adds the accumulator, called presses, to the x value from the signal of Keyboard.arrows. We can then map that value onto the show function. Now, when you run the application, you’ll get a running total of presses. The left decrements the count, and the right increments the count.
Let’s use the same general principle to count mouse movements. Signal.map applies a signal to a function. Signal.foldp folds over a signal, from the past:
| import Mouse |
| import Graphics.Element exposing (show) |
| |
| count signal = Signal.foldp (\_ n -> n + 1) 0 signal |
| main = Signal.map show (count Mouse.position) |
Navigate to the window on the right, move the mouse, and you’ll see a number that quickly counts mouse moves:
| 246 |
The count function takes a signal and adds one each time that signal changes. foldp works just like foldl did, but instead of folding across a list from the left, foldp folds through time, from the past. Our foldp function takes an anonymous function that adds one to a value, starts with an initial value of 0, and provides a signal. Our new signal will have values starting with 0 that increase each time the signal updates.
We can easily change the program to count mouse clicks:
| import Mouse |
| import Graphics.Element exposing (show) |
| |
| main = Signal.map show (count Mouse.clicks) |
| count signal = Signal.foldp (\_ n -> n + 1) 0 signal |
In this case, the count function counts the number of signal updates, which are mouse clicks. You can start to see how we can write code that respects the rules of functional programming, but is still reactive and easy to understand.
Believe it or not, foldp is the foundation of our game, as you’ll see on Day 3.
Let’s see how keyboard signals would work:
| import Graphics.Element exposing (show) |
| import Keyboard |
| |
| main = Signal.map show Keyboard.arrows |
Compile it, click on the right-hand window, and press the up and right arrows. You’ll see:
| { x = 1, y = -1 } |
You can intuitively see exactly what’s going on. map updates the text when the signal changes, so we get a clean program that tells us the state of the arrow keys, in a form that we can easily use. Since we can compose with functions, we can get more sophisticated.
Most user interfaces use more than one signal at once. For example:
Find out where a user clicked
Scroll based on window size and mouse position
Find the value of input fields when the user clicks a mouse
Drag and drop items
These problems are all combinations of signals. For more advanced applications, a simple map is not enough. Several other functions help us combine signals in more sophisticated ways. One of the most common user interface problems is to find where a user clicks.
Let’s use the function sampleOn. That function allows us to sample one signal when another updates, like this:
| import Graphics.Element exposing (show) |
| import Mouse |
| |
| clickPosition = Signal.sampleOn Mouse.clicks Mouse.position |
| main = Signal.map show clickPosition |
We build two signals, clickPosition and main. First, we create a signal with sampleOn. When the Mouse.Clicks signal updates, we’ll sample the most recent Mouse.position. The result is a new signal that returns a mouse position and changes whenever the user clicks a mouse. Next, we simply build our main signal. We map show onto our clickPosition signal. Simple. We can sample input controls in the same way.
Or, let’s say you’re implementing scrolling with a scroll bar. You need to find out how far down a page the mouse is, like this:
| import Graphics.Element exposing (show) |
| import Mouse |
| import Window |
| |
| div x y = show ((toFloat x) / (toFloat y)) |
| main = Signal.map2 (div) Mouse.y Window.height |
Run it and scroll on the right-hand side to get something like this:
| 0.42973977695167286 |
This example uses map2. Like map, this function maps functions onto signals, but uses two signals and two-argument functions.
First, to simplify type conversions, we create a version of division that takes integers and returns text. Next, we use map2 to map div onto two signals, Mouse.y and Window.height. Think about what a similar JavaScript program would look like. It doesn’t take too many examples to see Evan’s vision. Monitoring user inputs is a functional job.
Functional languages are great at transforming text. Elm is excellent for capturing text too. Here’s an example that takes some input, manipulates it, and puts it on the screen, using an HTML flow:
① | import String |
| import Html exposing (Html, Attribute, text, toElement, div, input) |
| import Html.Attributes exposing (..) |
| import Html.Events exposing (on, targetValue) |
| import Signal exposing (Address) |
| import StartApp.Simple as StartApp |
| |
② | main = StartApp.start { model = "", view = view, update = update } |
| |
③ | update newStr oldStr = newStr |
| |
④ | shout text = String.toUpper text |
| whisper text = String.toLower text |
| echo text = (shout text) ++ " " ++ (whisper text) |
| |
⑤ | view address string = |
| div [] |
| [ input |
| [ placeholder "Speak" |
| , value string |
| , on "input" targetValue (Signal.message address) |
| , myStyle |
| ] |
| [] |
| , div [ myStyle ] [ text (echo string) ] |
| ] |
| |
| myStyle = style [ ("width", "100%") ] |
We import the libraries we’ll need. String allows us to do string manipulation, and Html gives us access to various aspects of HTML including events, divs, and input fields.
Our one-line main function starts our application, initializing with an empty string, presenting a view, and handling updates with our update function, which will be called each time our entry field changes.
Next, our trivial update function simply returns the new value of the entry field each time the entry field updates.
Next, we define a couple of simple functions for working with text, the shout and whisper functions. We use those to build an echo function to transform the text. These functions know nothing about user interfaces. They just work on raw String data.
The next task is to build our HTML page. We add an input control and a div that will contain our changed text. The input control simply has the HTML elements Elm needs to render the control. The on function establishes a signal that will contain updates of the entry field. myStyle returns the style sheet for our HTML elements. The div function creates an HTML div with the specified contents and style.
Whew. That’s a lot of code packed into a short example. It may seem a little alien at first, but Elm’s worldview is the perfect complement to web front-end programming. Each user interface is just a stream of transformed user inputs. Now that we’ve seen how text works, let’s look at one more concept we’re going to need for our game. Instead of working with text, we will draw shapes based on user input.
In Elm, we can draw on the canvas with a full graphics library. We start with a collage with set dimensions, and then build shapes. We can transform the shapes by moving, scaling, or rotating them.
The figure shows a simple car. We’ll describe it in terms of functions. As you’d expect, we’ll use a combination of data structures and functions to do what we want.
elm/car.elm | |
| import Color exposing (..) |
| import Graphics.Collage exposing (..) |
| import Graphics.Element exposing (..) |
| |
| carBottom = filled black (rect 160 50) |
| carTop = filled black (rect 100 60) |
| tire = filled red (circle 24) |
| |
| main = collage 300 300 |
| [ carBottom |
| , carTop |> moveY 30 |
| , tire |> move (-40, -28) |
| , tire |> move ( 40, -28) ] |
First, we define a few basic shapes. We’ll define the basic dimensions of the shapes, and by default they’ll show up in the middle of the canvas. main is just a collage, which takes a width, a height, and a list of shapes, called forms in Elm. Each element of the list is just a shape. For example, carTop |> moveY 30 is just a rectangle moved 30 pixels vertically.
In this particular example, the figure is static. With Elm, animating that figure is nearly trivial. Say we have a rectangle with a form that looks like this:
| filled black (rect 80 10) |
When we build our game on Day 3, we’ll need a paddle. We can animate the paddle by mapping Mouse.x onto the function that draws this paddle, like this:
elm/paddle.elm | |
| import Color exposing (..) |
| import Graphics.Collage exposing (..) |
| import Graphics.Element exposing (..) |
| import Mouse |
| import Window |
| import Signal |
| |
| drawPaddle w h x = |
| filled black (rect 80 10) |
| |> moveX (toFloat x - toFloat w / 2) |
| |> moveY (toFloat h * -0.45) |
| |
| display (w, h) x = collage w h |
| [ drawPaddle w h x ] |
| |
| main = Signal.map2 display Window.dimensions Mouse.x |
Boom! Just like that, we have animation. We don’t have to worry about drawing the paddle over time, or remember the previous location of the paddle. Instead, we just worry about drawing the paddle right now, and letting the user input determine where to move it. Now, you have all of the foundation you’ll need to complete a game in Day 3. Let’s recap.
In Day 2, you learned about Elm’s primary purpose. Previous languages focused on callbacks or simply single-threaded code to create programs that respond to users. The cost was complexity or unresponsive interfaces. Functional programming languages have traditionally struggled with user interfaces because processing user input often involved changing state.
Elm solves both problems with signals, which are functions that represent values that change over time. By viewing user input as functions rather than data, the same beautiful functional programming techniques that express complex computations can be brought to bear on complex user interfaces.
We learned to transform signals by mapping functions onto them with map and map2. Each time, the result is a new signal. We also used other functions to combine signals and functions such as foldp, which maintains a running accumulator, and sampleOn, which determines exactly when we sample.
Finally, we displayed some text and graphics. We also mapped a display function onto a paddle, which will come in handy when we work on our game.
Use the online editor[60] to solve these problems interactively.
Examples of different signals available in Elm
The relationship between map and signals
A signal that fires every second
Write a signal to display the current mouse position, including whether a mouse button is pressed.
Write a signal to display the y value of the mouse when the button is pressed.
Use map and signals to draw your own picture at the current mouse position. Change the picture when a mouse button is pressed.
Write a program that counts up, from zero, with one count per second.
Use foldp to make the car move from left to right, and then right to left, across the bottom of the screen.
Make the car move faster when the mouse is farther to the right, and slower when the mouse is farther to the left.
That’s it for Day 2. Tomorrow, you’re going to combine everything we’ve learned so far to write a game. We’re going to go far beyond pong in this one, so fasten your seatbelt.