Mastering Elm
The goal of this workshop is to take you from square one to production in Elm.
- Syntax And Basics
- TEA
- Time
- Components in TEA
- Packages
- Library
- Runtime
- Embedding
- Ports
- Debugging
- Testing
- Legacy
- Gotchas
Syntax
Elm is an ML-like language
identity = λa. aIdentity
const identity = a => afunction identity(a){ return a; }identity = λa. aIdentity
identity = \a -> aidentity a = aTypes
identity : a -> a
identity x = xxs : 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.0ADTs 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 ++ " " ++ sAnatomy
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 -> bCodomain
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 Dadd3 : Int -> (Int -> (Int -> Int))
add3 : Int -> Int -> Int -> Int
add3 x y z = x + y + zElm is curried
const add3 = x => y => z => x + y + zfunction add3(x){
return function(y){
return function(z){
return (x + y + z);
}}}someFunc : Int -> Int -> Int
someFunc x y = add3 x y 18 - 100Whitespace 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 - 100Whitespace 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 = NothingMaybe
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 -> NothingMaybe
add : Maybe Int -> Maybe Int -> Maybe Int
add x y =
case (x, y) of
(Just x_, Just y_) -> Just (x_ + y_)
_ -> Nothingadd : 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 msgTEA 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 -> aDebug.crash : String -> aelm make Main.elm --debugTeeth 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 Vanishedwitnessed: ({ 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 Vanishedghost: 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 NothingEvil!
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