Intro to Elm 

by Claudia Doppioslash

@doppioslash - +ClaudiaDoppioslash - blog.doppioslash.com

14 April 2015 at Liverpool Javascript User Group

What is cool about Elm?

Elm is a Functional Reactive Language for interactive applications.

  • Functional Reactive Programming
  • Time Travelling Debugger
  • Hot code swapping
  • Avoid NULL pointers
  • Escape callback hell
  • Compiles to Javascript/Html/CSS

Time Travelling Debugger

- What is cool about Elm? -

Based on Bret Victor's talk Inventing on principle which also inspired LightTable. (See 12:00)

 

Demo: latest version of Evan's Mario example in elm-reactor. Note the tracing of the path and Hot Swapping of code changes (when it works).

 

Demo: Castle of Elm, change type of tiles with hot swapping, see the result immediately

Hot Code Swapping

- What is cool about Elm? -

If you have tried Erlang, you know the joy of not needing to recompile the whole project just to test out a tiny change.

 

Any change shows immediately and if tracing is on (when it works) you can see how it affects the past of the program as well.

- What is cool about Elm? -

Functional Reactive Programming

Elm is an opinionated language, which specifically wants you to build your code around its FRP implementation.

 

In Evan's thesis you can find an interesting survey of past FRP implementations, and the consequences of the choices they made.

 

Elm has opted to avoid lazyness as it makes it hard to predict the performance of the FRP implementation.

(RP has been adapted to OOP but it was born as FRP in Haskell)

Avoid Null Pointers

- What is cool about Elm? -

You most likely have known the pain of having to put null guards. No more. Union Types and pattern matching will spare you from that.

 

We'll show the Maybe type later on.

Escape race conditions

- What is cool about Elm? -

Single assignment makes concurrency easier.

(See Erlang)

 

It forces you to model your program as much without side effects as possible.

 

It also enables elm-reactor to let you scrub through the past of your program session as well. (!)

 

Wondering how this can work? Applying update function to model over time.

Escape callback hell

- What is cool about Elm? -

With Signals. A signal is a value that changes over time, which updates all the values that depend on it.

 

It can also be described as a stream of values. That also means that the changes in your program can be tracked (as seen in elm-reactor)

 

It really is like a normal mutable variable, but with controlled updates.

Compiles to Javascript/Html/CSS

The target platform is the web.

And the performance is very good.

 

- What is cool about Elm? -

What can you make with Elm?

FRP is well suited for GUIs and games.
If you have had to wrangle with MVC and MVVM and more of those acronyms in the past you know that UI programming is hardly solved.

What can you make with Elm?

Games

What can you make with Elm?

Web Apps

How to install Elm

  • Try Elm website
  • Elm Platform
  • elmenv
  • From source

- How to install Elm -

Just to open elm-lang.org/try in a browser. It includes an editor and a preview, it supports hot code reloading too.

Try Elm website

- How to install Elm -

Download  the installer from

elm-lang.org/Install.elm, it's available for Mac and Windows.

Elm platform

- How to install Elm -

elmenv

Simple management of Elm versions, inspired by projects like rbenvnvm, and phpenv.

- How to install Elm -

Try your luck with Cabal.

Elm is implemented in Haskell.

Halcyon can be helpful.

From source

- How to install Elm -

  • Sublime Text
  • Atom
  • Emacs
  • Vim

Editor Support

Hello World

import Graphics.Element (..)
import Text (..)

main : Element
main =
  plainText "Hello, World!"

Import modules ->

<- Import all symbols in the module

Type signature ->

<- Main function

- Hello World -

These are the 3 necessary elements of an Elm program:

  • Module imports
  • Type Signatures
  • Main function

If you've ever tried Haskell you'll find the syntax familiar.
The type signature goes before the function.

But Elm has type inference so most of the time you'll be able to skip writing it down.

import Graphics.Element (..)

Module imports

Packages are found on:

package.elm-lang.org

Will import all functions in Graphics.Elements, you'll be able to use them without prefix.

import Graphics.Element as el

You'll be able to use them with prefix el.namefunction

- Hello World -

square : Int -> Int

Type signatures

Is the signature of a function that takes an Integer and returns an Integer.

add : Int -> Int -> Int

Is the signature of a function that takes two Integers and returns an Integer.

- Hello World -

Main function

The main function is the entry point of program execution, so you need one, or your code won't run.

 

It is wired to one or more Signals that drive the whole application.

- Hello World -

Fundamental Concepts

  • Signals
  • Union Types
  • Pattern Matching
  • Foldp

Signals

import Graphics.Element (..)
import Signal (Signal, map)
import Mouse
import Text (asText)

main : Signal Element
main =
  map asText Mouse.position

[from examples]

Signals are values that change over time. They are Inputs that can only be transformed, merged and filtered.

In practice it's similar to how a spreadsheet works.

Simple program which shows the mouse position.

- Fundamental Concepts -

Union Types

A union type is a way to put together many different types. Somewhat a more powerful enum.

type Action
    = Increment
    | Decrement
type Action
    = Increment Int
    | Decrement Int

Plain enumeration

Enumeration + Data

- Fundamental Concepts -

Pattern Matching

It's a much more powerful switch case statement. Made to be use in combination with Union Types, it will accept any type the Union Type has attached.

type alias Model = Int
update : Action -> Model -> Model
update action model =
  case action of
    Increment -> model + 1
    Decrement -> model - 1
type alias Model = Int
update : Action -> Model -> Model
update action model =
  case action of
    Increment value -> model + value
    Decrement value -> model - value

Plain enumeration

Enumeration + Data

- Fundamental Concepts -

Maybe Type

type Maybe Int
    = Just Int
    | Nothing

The Maybe type allows us to avoid null checks. If there is a value it will be Just value if there isn't it will be Nothing.

- Fundamental Concepts -

Match Maybe Type

String.toInt : String -> Maybe Int

toMonth : String -> Maybe Int
toMonth rawString =
    case String.toInt rawString of
      Nothing -> Nothing
      Just n ->
          if n > 0 && n <= 12 then Just n else Nothing

- Fundamental Concepts -

Foldp

- Fundamental Concepts -

Folding over the past, a function that is applied on a signal in time.

Where the state is, without breaking our system of getting updated values by applying functions.

foldp : (a -> b -> b) -> b -> Signal a -> Signal b

Its arguments are:

  • a function which takes two args
  • a default value for the output signal
  • an input signal

The two args of the function are the current value of the input signal, and the past value or default value if there is no past value.

Foldp

- Fundamental Concepts -

apply this function at every new value in the Signal and use the result as an input when applying it at the next value.

Architecture

  • Model - a full definition of the application's state

  • Update - a function applied on the model that creates a new model that will have the view function applied to 

  • View - a way to visualize our application state with HTML, applied to the Model data structure 

  • Signals - the signals and inputs necessary to manage events (inputs will be signals)

[example from elm-architecture]

- Architecture -

Basic Pattern

-- MODEL

type alias Model = { ... }


-- UPDATE

type Action = Reset | ...

update : Action -> Model -> Model
update action model =
  case action of
    Reset -> ...
    ...


-- VIEW

view : Model -> Html
view =
  ...

<- type alias

<- Union type action

<- update function

<- View update function

- Architecture -

Model

A data structure which contains our state. We skipped over Records so we'll stick to a plain Int.

type alias Model = Int

- Architecture -

Update

type Action = Increment | Decrement

update : Action -> Model -> Model
update action model =
  case action of
    Increment -> model + 1
    Decrement -> model - 1
  • A union type which enumerates the possible actions, with or without attached data.
  • The function which by applying the action to the model creates the new model state. 

- Architecture -

View

view : Model -> Html
view model =
  div []
    [ button [ onClick (Signal.send actionChannel Decrement) ] [ text "-" ]
    , div [ countStyle ] [ text (toString model) ]
    , button [ onClick (Signal.send actionChannel Increment) ] [ text "+" ]
    ]

countStyle : Attribute
countStyle = 
  style
    [ ("font-size", "20px")
    , ("font-family", "monospace")
    , ("display", "inline-block")
    , ("width", "50px")
    , ("text-align", "center")
    ]

The function which applied to the model creates the new view state.

main : Signal Html
main =
  Signal.map view model

model : Signal Model
model =
  Signal.foldp update 0 (Signal.subscribe actionChannel)

actionChannel : Signal.Channel Action
actionChannel =
  Signal.channel Increment

- Architecture -

Inputs/Signals

The main function is driven by the Signals. It is updated every time we click on the buttons we create in the view.

(Signal.send actionChannel Decrement)

From buttons ->

Elm Tools and Workflows

  • Make your project
  • elm-package
  • elm-reactor
  • elm-make

- Elm Tools and Workflow -

Elm Try website

elm-package

- Elm Tools and Workflow -

Use elm-package to install community packages:

elm-package install user/project

elm-package is sandboxed by default. All packages are installed on a per-project basis, so all your projects dependencies are independent.

[from elm web site]

You can also put the dependencies of a project in the elm-package.json file, for convenient dependency handling.

- Elm Tools and Workflow -

elm-make

Workflow:

  • compile in elm-make
  • open in browser

Command:

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

elm-reactor

Workflow:

  • edit with editor
  • start elm-reactor
  • open browser

- Elm Tools and Workflow -

Command:

elm-reactor

In the root of your elm files.

Castle of Elm

Example of a more complex program

Castle of Elm is a small proof of concept made for #7DRL (7 days rogue like) (more like 3 days I got to spend on it)

 

It's supposed to be a Castle of the Winds clone, but I only got around to making a flexible tile system with character collisions.

 

There is a supposedly nice Javascript library to make Roguelikes but I wanted to really try and stick to FRP from top to bottom and didn't want to mix in OOP/imperative style.

The Elm experience

Compared to my coding in other functional and OOP languages the main takeaways were:

  • The type system helps a lot. If some code could compile it would run (logic errors remain possible)
  • elm-reactor makes looking into any value and the history of any value a breeze
  • Hot swapping, though it needs to be rebooted often still, is excellent for visual tweaks, when one would blindly change code and rebuild.
  • it's relatively clear how to structure an Elm program according to the latest Elm Architecture docs, and it seems to scale fine so far as I've pushed it

Make a project from scratch

We'll need elm-make elm-package and elm-reactor.

 

A simple mkdir will do.

 

Inside we'll make an appropriate elm-package.json. We only need elm-lang/core but there are many available libraries.

 

Now we can make a .elm file to start coding in.

Draft

I find that union types are really handy when getting an idea of what you'll want in your program and how it will look like.

This is the first Action type I made, which is a pretty concise and faithful translation of Castle of the Winds' game design.

type Action
= NoOp
| Move Direction
| Use (Thing, Tile) 
| Open Thing
| Close Thing
| Rest
| Disarm Trap

| Search Tile
| Get Tile
| FreeHand
| ShowMap
| ShowInventory
| TakeStairs Tile
| MoveMoveOver Tile

Set up signals

The next step was choosing a signal to drive the program. I could sample the inputs on the render frame tick, but for now I decided to just stick to updating when the user presses the arrow keys.

input : Signal (Float, Keys)
input = 
let 
    delta = Signal.map (\t -> t/20) (fps 60)
    deltaArrows = Signal.map2 (,) delta (Signal.map
    (Debug.watch "arrows") Keyboard.arrows)
in
    Signal.sampleOn delta deltaArrows

Sample on fps tick

<- Debug.watch makes it visible in elm-reactor

- Set up signals -

input : Signal Keys
input =
Signal.map (Debug.watch "arrows") Keyboard.arrows

Just observe Keyboard arrows signal

main : Signal Element
main = map view (foldp update model input)

We're just using input to drive the foldp and our view as well, all chained together.

Records

A record is quite simply a bunch of named values stitched together. It's immutable, there is a specific syntax to update it and you can make types out of it.

There is the ability of coding to an interface but we don't need it now.

type alias Character =
{ x : Float
, y : Float
, dir: Direction
}
pcState : Character
pcState =
{ x = 0
, y = 0
, dir = Right
} 

 

type alias of a record

A record in use

Updating a record

{ pc | y <- pc.y + 1, dir <- Up }

Scaling the Architecture

The program structure we saw before scales surprisingly well. 

 

Empirical way to cope with more:

  1. add new data needed to model
  2. pass it around in the update function
  3. update the nested bits of data

Use modules when code size becomes too much.

Updating Nested Records

model : Model
model =
{ grid = mainGrid
, gridSide = gridWidth
, pc = pcState }
update : Direction -> Model -> Model
update dir model =
    model
        |> movepc dir
movepc : Direction -> Model -> Model
movepc dir model =
let updatePc pc dir =
    case dir of
        Up -> { pc | y <- pc.y + 1, dir <- Up }
        Down -> { pc | y <- pc.y - 1, dir <- Down }
     Left -> { pc | x <- pc.x - 1, dir <- Left }
        Right -> { pc | x <- pc.x + 1, dir <- Right }
        None -> pc
in 
    { model | pc <- updatePc model.pc dir }

< we call the function in the let

Updating Nested Records

Refs:

thanks to elm IRC channel

https://github.com/evancz/focus/tree/1.0.0

https://github.com/evancz/elm-todomvc/blob/master/Todo.elm#L102

The Grid

type Tile
| BackGround BackGroundTile
type BackGroundTile
= Floor
| WallOver WallTile
| Wall
| Water
type alias WallTile = { 
r: WallJunction, 
l: WallJunction, 
u: WallJunction, 
d: WallJunction }
fffe : WallTile
fffe = { r = Flat, u = Flat, l = Flat, d = Empty }

Grid:

[BackGround (WallOver ffee), ... ]

elm-reactor and how it helps

Debug.watch "name"

Debug.trace "name"

Will show the value in elm-reactor

Will trace the position visually in elm-reactor

Changing grid with hot code reloading.

elm-reactor implementation

  • Pausing: intercepting by wrapping elm-notify and recording the input (so it works even if you have code swapped)
  • Running backward: achieved by resetting and going forward again
  • Time slider: caches the states at intervals

Elm 0.15 Preview

  • Tasks: asynchronous operations that may fail
  • Towards eradicating runtime errors (with Maybe)
  • Import syntax changes

 

Summary

You should now:

  • Know what makes Elm interesting
  • Have it installed
  • Being able to architect a simple program
  • Run your programs with the time travelling debugger

Extra

Interestingly, however, we will see that lazy evaluation is actually crucial to building efficient data structures in purely functional languages (i.e. without mutable variables).

To bridge this gap, Elm (and most other ML dialects) do provide mechanisms for lazy evaluation on top of the eager semantics.

References

Fin.

Intro to Elm - Liverpool Javascript User Group

By Claudia Doppioslash

Intro to Elm - Liverpool Javascript User Group

  • 3,760