Explicitly define all actions that can modify state function addTodo() { return { type: 'USER_ADD_TODO', text: 'Buy Milk' } } @Jack_Franklin, bit.ly/elm-polyconf 24
Even more explicit because the compiler says so update msg model = case msg of NewUser name -> { model | user = name } This `case` does not have branches for all possibilities. 22|> case msg of You need to account for the following values: LogOut Add a branch to cover this pattern! @Jack_Franklin, bit.ly/elm-polyconf 34
Union Types 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 @Jack_Franklin, bit.ly/elm-polyconf 51
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! @Jack_Franklin, bit.ly/elm-polyconf 52
Type aliases type alias Person = { name : String , age : Int } incrementAge : Person -> Person incrementAge person = { person | person.age = person.age + 1 } @Jack_Franklin, bit.ly/elm-polyconf 53
! Clearer code, typed in your domain specific objects. ! Compiler can guarantee you're meeting the type requirements. ! No more 'undefined is not a function' ! @Jack_Franklin, bit.ly/elm-polyconf 54
Immutability brings guarantees var person = { name: 'Jack', age: 24 }; incrementAge(person); // has this mutated? // does it return a new person? // #javascript @Jack_Franklin, bit.ly/elm-polyconf 57
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 @Jack_Franklin, bit.ly/elm-polyconf 58
Modules ! Everything is scoped ! Modules explicitly declare what they expose ! Modules explicitly declare what they import @Jack_Franklin, bit.ly/elm-polyconf 59
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) ] @Jack_Franklin, bit.ly/elm-polyconf 62
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 @Jack_Franklin, bit.ly/elm-polyconf 64
Commands and Subcriptions • Cmd : an async thing that Elm should run for you • Sub : a subscription to some data you care about that might change (We'll come back to these). @Jack_Franklin, bit.ly/elm-polyconf 66
Adjustment time This does take time to get used to • Syntax • Types • Immutablility • Compiling! • Maybe and explicit error handling @Jack_Franklin, bit.ly/elm-polyconf 67
When you apply a pattern everywhere it ends up becoming a guarantee: that 100% of your code will follow that pattern. Once you have that guarantee you can build powerful developer tools or cross module features. -- Everywhereness as a Foundation, André Staltz @Jack_Franklin, bit.ly/elm-polyconf 68
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 @Jack_Franklin, bit.ly/elm-polyconf 69
Thirdly, define your update: update : Msg -> Model -> Model update msg model = case msg of Increment -> model + 1 Decrement -> model - 1 @Jack_Franklin, bit.ly/elm-polyconf 76
! We left the view until last. ! Explained all our logic before the UI. ! Notice how easy update would be to test. @Jack_Franklin, bit.ly/elm-polyconf 80
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. @Jack_Franklin, bit.ly/elm-polyconf 84
Firstly, define the model: type alias GithubPerson = { name : String , company : String } type alias Model = { username : String , githubPerson : Maybe GithubPerson } @Jack_Franklin, bit.ly/elm-polyconf 88
Thirdly, define your update (and note the new type) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of FetchError error -> -- deal with error here in reality NewGithubData person -> ( { model | githubPerson = Just person }, Cmd.none ) FetchGithubData -> ( model, fetchGithubData model.username ) @Jack_Franklin, bit.ly/elm-polyconf 90
NewGithubData person -> ( { model | githubPerson = Just person }, Cmd.none ) -- Cmd.none === do nothing FetchGithubData -> ( model, fetchGithubData model.username ) --- fetchGithubData returns a command --- which Elm will run for us @Jack_Franklin, bit.ly/elm-polyconf 91
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!" ] ] @Jack_Franklin, bit.ly/elm-polyconf 93
Fetching Data • Decoding JSON from an API into an Elm record. • Use Elm's HTTP library to make the request. • Code in the GitHub repo! • Come and grab me if you'd like to see it in person. @Jack_Franklin, bit.ly/elm-polyconf 106
Semantic versioning ensured. ~/git/elm-statey > elm package diff Comparing jackfranklin/elm-statey 2.0.0 to local changes... This is a MAJOR change. ------ Changes to module Statey - MAJOR ------ Changed: - makeState : String -> Statey.State + makeState : Statey.State @Jack_Franklin, bit.ly/elm-polyconf 115
Defining your application step by step 1. Define your model. 2. Define your actions. 3. Define your update function. 4. Define your view. 5. Repeat. @Jack_Franklin, bit.ly/elm-polyconf 131