Recommended Reading
➡ Unidirectional User Interface Architectures by
André Staltz
Slide 40
Slide 40 text
No content
Slide 41
Slide 41 text
Explictly deal with all user interactions and state
changes
function update(action, state) {
switch (action.type) {
case 'NEW_USER':
return Object.assign({}, state, {
user: { name: action.name }
});
}
}
incrementAge person =
{ person | age = person.age + 1 }
add x y =
x + y
addTwo =
add 2
Slide 54
Slide 54 text
Self documenting
Slide 55
Slide 55 text
Types
add : Int -> Int -> Int
add x y =
x + y
isEven : Int -> Bool
isEven x =
n % 2 == 0
Slide 56
Slide 56 text
Union Types
type Filter
= ShowAll
| ShowCompleted
| ShowActive
Slide 57
Slide 57 text
type Filter
= ShowAll
| ShowCompleted
| ShowActive
showTodos : Filter -> List Todo -> List Todo
showTodos filter todos =
case filter of
ShowAll -> todos
ShowCompleted -> List.filter (\t -> t.complete) todos
ShowActive -> List.filter (\t -> not t.complete) todos
Slide 58
Slide 58 text
Union Types
! They can be checked by the compiler (typos are
spotted)
! Compiler ensures all are dealt with in case ... of
! Easy to change / add a new one: add it and fix
each compiler error!
Slide 59
Slide 59 text
Type aliases
type alias Person =
{ name : String
, age : Int
}
incrementAge : Person -> Person
incrementAge person =
{ person | person.age = person.age + 1 }
Slide 60
Slide 60 text
! Clearer code, typed in your domain specific
objects.
! Compiler can guarantee you're meeting the type
requirements.
! No more 'undefined is not a function' !
Slide 61
Slide 61 text
Robust
Slide 62
Slide 62 text
Immutability brings guarantees
var person = { name: 'Jack', age: 24 };
incrementAge(person);
// has this mutated?
// does it return a new person?
Slide 63
Slide 63 text
No content
Slide 64
Slide 64 text
Sweet, sweet Elm
let
person = { name = "Jack", age = 24 }
in
incrementAge person
! person is untouched
! incrementAge has to return a new person
! goodbye mutation bugs
Slide 65
Slide 65 text
Pure Functions
Elm functions are always pure.
let sum = (a, b) => a + b; //PURE
sum(2, 2) // => ALWAYS 4
let otherSum = (a, b) => window.foo + a + b; //IMPURE
otherSum(2, 2) // => who knows, dependent on window.foo
Slide 66
Slide 66 text
Dealing with nothing
Slide 67
Slide 67 text
I call it my billion-dollar
mistake.
Slide 68
Slide 68 text
It was the invention of the
null reference in 1965.
Slide 69
Slide 69 text
This has led to innumerable errors, vulnerabilities,
and system crashes
-- Tony Hoare
Slide 70
Slide 70 text
You might say
it's a...
Slide 71
Slide 71 text
...hoare-able
mistake!
Slide 72
Slide 72 text
(You may
leave)
Slide 73
Slide 73 text
No null: how do you
represent a value being
present or empty?
Slide 74
Slide 74 text
render() {
return
{ this.props.apiData.username }
;
}
Slide 75
Slide 75 text
Maybe
Slide 76
Slide 76 text
Maybe
type Maybe a =
Just a
| Nothing
It's either Just some value, or Nothing.
Slide 77
Slide 77 text
As a value, I am:
→ Just the integer 5
→ or, I am Nothing
I have the type Maybe Int
Slide 78
Slide 78 text
Get the first thing from the list, and double it2
let list = [1, 2, 3] in
(List.head list) * 2
But what if list = [] ?
2 this code is not valid Elm, becuase List.head returns Maybe
Slide 79
Slide 79 text
‼
Slide 80
Slide 80 text
Maybe
type alias Model = {
user : Maybe User
}
view : Model -> Html Msg
view model =
case model.user of
Nothing ->
div [] [ text "No user!" ]
Just user ->
div [] [ text ("Logged in as " ++ user.name) ]
Slide 81
Slide 81 text
You must handle all cases
of missing / pending data
Slide 82
Slide 82 text
Task
A module for async actions that might fail (HTTP).
Task errType successType
Task String User
- if it fails, fail with a String
- if it succeeds, succeed with a User
Slide 83
Slide 83 text
Commands
an async thing that Elm should run for you (HTTP
requests)
Slide 84
Slide 84 text
Learning curve!
Slide 85
Slide 85 text
The Elm compiler make sure that 100% of your code
is thoroughly checked against corner cases and
error cases. This everywhereness becomes a
guarantee. And it is only because of this guarantee
that Elm programs have virtually no runtime errors.
-- Everywhereness as a Foundation, André Staltz
Slide 86
Slide 86 text
No content
Slide 87
Slide 87 text
No content
Slide 88
Slide 88 text
Building actual apps in
Elm
Slide 89
Slide 89 text
The Elm Architecture
Slide 90
Slide 90 text
The three parts:
model : Model
view : Model -> Html Msg
update : Msg -> Model -> Model
Slide 91
Slide 91 text
What changed?
Slide 92
Slide 92 text
No content
Slide 93
Slide 93 text
Building a counter app
Slide 94
Slide 94 text
First, define your Model
type alias Model = Int
initialModel : Model
initialModel = 0
Slide 95
Slide 95 text
Secondly, define your Msgs
type Msg = Increment | Decrement
Slide 96
Slide 96 text
Thirdly, define your update:
update : Msg -> Model -> Model
update msg model =
case msg of
Increment -> model + 1
Decrement -> model - 1
Slide 97
Slide 97 text
Fourthly, define your view:
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
Slide 98
Slide 98 text
Finally, hook it all up!
main =
Html.App.beginnerProgram
{ model = initialModel
, view = view
, update = update
}
Slide 99
Slide 99 text
No content
Slide 100
Slide 100 text
! We left the view until last.
! Explained all our logic before the UI.
! Notice how easy update would be to test.
Slide 101
Slide 101 text
Side Effects
Slide 102
Slide 102 text
Explicitly model side effects.
Hand off to Elm, it will hand back later.
Keeps functions pure, and async easier to reason
about.
Slide 103
Slide 103 text
Commands
Slide 104
Slide 104 text
Whenever you need to perform some background
work, you have to give Elm a command.
Elm will go off, perform the command, and call your
update function once it's done.
Slide 105
Slide 105 text
The Elm Architecture,
Part 2
Slide 106
Slide 106 text
model : Model
view : Model -> Html Msg
update : Msg -> Model -> (Model, Cmd Msg)
Slide 107
Slide 107 text
No content
Slide 108
Slide 108 text
Fetching someone's
GitHub data.
Slide 109
Slide 109 text
Firstly, define the model:
type alias GithubPerson =
{ name : String
, company : String
}
type alias Model =
{ username : String
, githubPerson : Maybe GithubPerson
}
Slide 110
Slide 110 text
Secondly, define your Msgs
type Msg
= NewGithubData GithubPerson
| FetchGithubData
| FetchError Http.Error
Slide 111
Slide 111 text
Thirdly, define your update:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NewGithubData person ->
( { model | githubPerson = Just person }, Cmd.none )
FetchGithubData ->
( model, fetchGithubData model.username )
...
Slide 112
Slide 112 text
NewGithubData person ->
( { model | githubPerson = Just person }, Cmd.none )
-- Cmd.none === do nothing
Slide 113
Slide 113 text
FetchGithubData ->
( model, fetchGithubData model.username )
--- fetchGithubData returns a command
--- which Elm will run for us
Slide 114
Slide 114 text
No content
Slide 115
Slide 115 text
Fourthly, define your view:
view : Model -> Html Msg
view model =
case model.githubPerson of
Just person ->
div []
[ text (person.name ++ ", " ++ person.company) ]
Nothing ->
div [] [
button [ onClick FetchGithubData ] [ text "Load!" ]
]