Mastering Elm

The goal of this workshop is to take you from square one to production in Elm.

  1. Syntax And Basics
  2. TEA
  3. Time
  4. Components in TEA
  5. Packages
  6. Library
  7. Runtime
  8. Embedding
  9. Ports
  10. Debugging
  11. Testing
  12. Legacy 
  13. Gotchas

Syntax

Elm is an ML-like language

identity = λa. a

Identity

const identity = a => a
function identity(a){ return a; }
identity = λa. a

Identity

identity = \a -> a
identity a = a

Types

identity : a -> a
identity x = x
xs : List Int
xs = [1,2,3]
empty : List a
empty = []
hw : String 
hw = "hello world"

Primitives

w : Char
w = 'a'
y : Int 
y = 0
x : String
x = "hello"
z : Float
z = 5.0

ADTs and Records

type Teeth 
    = Jagged 
    | Twisted 
    | Rotten

type alias Monster = 
    { name : String
    , age : Float
    , teeth : Teeth
    }

ADTs

type Miles = Miles Int

three : Miles
three = Miles 3

add : Miles -> Miles -> Miles
add (Miles x) (Miles y) = Miles (x + y)

ADTs

type Foo = Bar Int | Foo String Int | Baz String

fooish : Foo
fooish = Bar 3

displayWith : Foo -> String -> String
displayWith foo name = case foo of
   Bar x -> name ++ " " ++ toString x
   Foo s x -> name ++ " " ++ s ++ ": " ++ toString x
   Baz s -> name ++ " " ++ s

Anatomy

type Foo = Bar

Type

Term/Value

Anatomy

type alias Foo = Bar

Type

Type

Anatomy

x : Maybe Int
x = Just 3

Type

Term

type of

assign/bind

Anatomy

f : Int -> Maybe Int
f x = if x > 0 
      then Just x 
      else Nothing

Anatomy

f : a -> b

Codomain

Domain

Anatomy

type Foo a = B a
type Baz = C Int | D
  • Types combine with Types
  • Terms combine with Terms
     
  • Types and Terms do not combine
x : Foo Baz
x = B (C 12)
Foo (C 20) 
Foo D
add3 : Int -> (Int -> (Int -> Int))
add3 : Int -> Int -> Int -> Int
add3 x y z = x + y + z

Elm is curried

const add3 = x => y => z => x + y + z
function add3(x){
   return function(y){
      return function(z){
         return (x + y + z);
      }}}
someFunc : Int -> Int -> Int
someFunc x y = add3 x y 18 - 100

Whitespace is Function Application

add3(x)

add3 x : Int -> Int -> Int

add3(x)(y)

add3 x  y : Int -> Int

add3(x)(y)(18)

add3 x  y 18 : Int

someFunc : Int -> Int -> Int
someFunc x y = add3 x y 18 - 100

Whitespace is Function Application

add3(x)(y)(18)

add3 x  y 18 : Int

{

(-)(add3(x)(y)(18))

(-) (add3 x  y 18) : Int -> Int

(-)(add3(x)(y)(18))(100)

(-) (add3 x  y 18)(100) : Int

Maybe

type Maybe a = Just a | Nothing

threeInThere : Maybe Int
threeInThere = Just 3

putIntoMaybe : a -> Maybe a
putIntoMaybe = Just

notThere : Maybe a
notThere = Nothing

Maybe

add : Maybe Int -> Maybe Int -> Maybe Int
add x y = 
  case x of
    Just x_ -> 
      case y of
        Just y_ -> Just (x_ + y_)
        Nothing -> Nothing
    Nothing -> Nothing

Maybe

add : Maybe Int -> Maybe Int -> Maybe Int
add x y = 
  case (x, y) of
    (Just x_, Just y_) -> Just (x_ + y_)
    _ -> Nothing
add : Maybe Int -> Maybe Int -> Maybe Int
add = map2 (+)

The Elm Architecture

(aka) TEA

TEA is a Pattern

TEA can be represented in almost any language. In Elm, this pattern is enforced, and inescapable.

 

Other implementations of TEA include:
PUX - A PureScript library backed by React.js

Spork - A PureScript library backed by Halogen-VDOM

Franken Elm - React + Redux + TypeScript

Gloss - A Haskell library for OpenGL
 

TEA in Elm

program
  : { init : (model, Cmd msg)
    , update : msg -> model -> (model, Cmd msg)
    , subscriptions : model -> Sub msg }
  -> Program Never model msg

TEA in Elm

{ init : (model, Cmd msg)
, update : msg -> model -> (model, Cmd msg)
, subscriptions : model -> Sub msg 
}

TEA has 5 Parts

Two Types

Three Terms

  • Msg
    Discrete messages in
    our application
  • Model
    The state of our app
  • Update
    State transitions in response to Msgs.
  • View
    How to draw the UI based on the Model.
  • Init
    The starting state for
    the app.
type Count = Count

type alias Knocks = Int

main : Program Never Knocks Count
main = program

    { init   = (0, Cmd.none)

    , update = \Count knocks -> (knocks + 1, Cmd.none)

    , view   = \knocks ->
        Html.div
            [ Html.Attributes.style
                    [ ("padding", "20px") ] ]
            [ Html.h3 []
                [ Html.text ("Knocks: " ++ toString knocks) ]
            , Html.button
                [ Html.Events.onClick Count ]
                [ Html.text "Count" ] ]

    , subscriptions = \knocks -> Sub.none }

Runtime

Start Time

0

type Count = Count

type alias Knocks = Int

main : Program Never Knocks Count
main = program

    { init   = (0, Cmd.none)

    , update = \Count knocks -> (knocks + 1, Cmd.none)

    , view   = \knocks ->
        Html.div
            [ Html.Attributes.style
                    [ ("padding", "20px") ] ]
            [ Html.h3 []
                [ Html.text ("Knocks: " ++ toString knocks) ]
            , Html.button
                [ Html.Events.onClick Count ]
                [ Html.text "Count" ] ]

    , subscriptions = \knocks -> Sub.none }

Runtime

Start Time

0

type Count = Count

type alias Knocks = Int

main : Program Never Knocks Count
main = program

    { init   = (0, Cmd.none)

    , update = \Count knocks -> (knocks + 1, Cmd.none)

    , view   = \knocks ->
        Html.div
            [ Html.Attributes.style
                    [ ("padding", "20px") ] ]
            [ Html.h3 []
                [ Html.text ("Knocks: " ++ toString knocks) ]
            , Html.button
                [ Html.Events.onClick Count ]
                [ Html.text "Count" ] ]

    , subscriptions = \knocks -> Sub.none }

Runtime

Idle Time

0

type Count = Count

type alias Knocks = Int

main : Program Never Knocks Count
main = program

    { init   = (0, Cmd.none)

    , update = \Count knocks -> (knocks + 1, Cmd.none)

    , view   = \knocks ->
        Html.div
            [ Html.Attributes.style
                    [ ("padding", "20px") ] ]
            [ Html.h3 []
                [ Html.text ("Knocks: " ++ toString knocks) ]
            , Html.button
                [ Html.Events.onClick Count ]
                [ Html.text "Count" ] ]

    , subscriptions = \knocks -> Sub.none }

Runtime

Click Time

0

type Count = Count

type alias Knocks = Int

main : Program Never Knocks Count
main = program

    { init   = (0, Cmd.none)

    , update = \Count knocks -> (knocks + 1, Cmd.none)

    , view   = \knocks ->
        Html.div
            [ Html.Attributes.style
                    [ ("padding", "20px") ] ]
            [ Html.h3 []
                [ Html.text ("Knocks: " ++ toString knocks) ]
            , Html.button
                [ Html.Events.onClick Count ]
                [ Html.text "Count" ] ]

    , subscriptions = \knocks -> Sub.none }

Runtime

Click Time

0

1

type Count = Count

type alias Knocks = Int

main : Program Never Knocks Count
main = program

    { init   = (0, Cmd.none)

    , update = \Count knocks -> (knocks + 1, Cmd.none)

    , view   = \knocks ->
        Html.div
            [ Html.Attributes.style
                    [ ("padding", "20px") ] ]
            [ Html.h3 []
                [ Html.text ("Knocks: " ++ toString knocks) ]
            , Html.button
                [ Html.Events.onClick Count ]
                [ Html.text "Count" ] ]

    , subscriptions = \knocks -> Sub.none }

Runtime

Click Time

1

1

Let's Use TEA

TEA Components

Components have
1 to 5 of the things

Less is more.

Let's look at Button
and Dropdown

Elm Packages

Nice things

  • Enforced semantic versioning
     
  • Guaranteed documentation
     
  • Guaranteed not to have side effect
     
  • Excellent community and Eco-system

Less nice things

  • No ability to delete
     
  • No private server support
     
  • No ability to publish side effects
     
  • Packages age rapidly

All the goodies are in Extra

  • basics-extra
  • array-extra
  • dict-extra
  • html-extra
  • json-extra
  • list-extra
  • maybe-extra
  • random-extra
  • result-extra
  • string-extra
  • svg-extra
  • elm-date-extra
  • elm-color-extra
  • elm-cmd-extra
  • elm-set-extra
  • elm-function-extra
  • elm-tuple-extra

Do not fear installing extras,
they are not spooky.

Packages owners of note

  • elm-lang
  • elm-community
  • evancz
  • NoRedInk
  • rtfeldman
  • gdotdesign
  • eeue56
  • mgold
  • fresheyeball
  • etaque
  • krisajenkins
  • mdgriffith

Use packages from these sources when possible.

Some packages are special

Packages that do IO are generally only in the elm-lang namespace.

 

Some packages expose their own version of `program`. You will want to identify those early in your development, as changing out the program function has deep implications.

Elm on npm

  • elm-webpack-loader
    For those of you who plan to use webpack
  • elm-test
    CLI tool for testing (we will be using this)
  • elm-css
    Elm as a CSS pre-processor
  • elm-react
    For embedding Elm in React apps
  • elm-github-install
    Jail break for Elm packages

Let's write a Library

Debugging

Means available

Debug.log : String -> a -> a
Debug.crash : String -> a
elm make Main.elm --debug

Teeth and Claws

Exploiting Eager

encounter : Witness -> Ghost -> Result Causality Witness
encounter witness ghost = 
  let
     _ = Debug.log "witnessed" (witness, ghost)
  in 
     case compareGhosts ghost AnneBoleyn of 
        LT -> 
          Ok witness

        _ -> 
          Err Vanished

witnessed: ({ name = "Jeff"} , FlyingDutchman)

Inline

encounter : Witness -> Ghost -> Result Causality Witness
encounter witness ghost = 
   case compareGhosts (Debug.log "ghost" ghost) AnneBoleyn of 
      LT -> 
        Ok witness

      _ -> 
        Err Vanished

ghost: FlyingDutchman

Not yet implimented

enhance : ColdAudio -> HotAudio
enhance = Debug.crash "not yet implemented `enhance`"

recordAndAnalyze : Time -> Time -> Maybe Conclusion
recordAndAnalyze start now = 
    if start >= now then 
       record now
       |> enhance
       |> analyze
       |> Just 
    else Nothing

Evil!

revealMystery : Bool -> Response
revealMystery handle = 
  case handle of
     False -> 
       Relax

     True -> 
       Debug.crash "you can't handle the truth"

    

This function is not total!

Things to watch for

  • Regex
  • Arrays
  • Divide or mod by 0
  • Function equality
  • Json.Value
  • Bad port
  • Rare bad Html
  • lazy optimization

Legacy

Identify​ 

  • Go from the bottom up
     
  • Find a section you can convert
     
  • Find a new feature you can write

What won't work 

  • Going from the top down
    (Elm is unfriendly to having JS embedded)

Identify​ 

  • Go from the bottom up
     
  • Find a section you can convert
     
  • Find a new feature you can write

Problems

  • Code duplication of runtime.js
     
  • Code duplication of app code
     
  • Elm is unfriendly to
    wrapping JS components
     
  • Port pain

Resolutions

  • Future Elm will support compilation
    without runtime.js
     
  • Google Closure Compiler
     
  • Add manual deduping to the build
     
  • Learn to write native code
    (not recommended)
     
  • Manage JS embedding through ports
     
  • onLoad hack

Gotchas

State is still State

Elm programs do
not compose

Elm community can be resistant to teaching FP concepts

Elm's feature set can be unstable between releases

Elm can crash at runtime

Elm is not
general purpose

Elm makes some FP abstractions painful to work with

Do not fear Elm.

Uncover the mysteries of FP frontend development,
and get out there and write some awesome code!

Mastering Elm

By fresheyeball

Mastering Elm

  • 637