Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PolyConf: Elm for JS Developers

PolyConf: Elm for JS Developers

A talk on Elm and why it's cool.

Jack Franklin

June 30, 2016
Tweet

More Decks by Jack Franklin

Other Decks in Technology

Transcript

  1. We must accept that complex applications are hard to build

    @Jack_Franklin, bit.ly/elm-polyconf 9
  2. Components in some form • Angular 2 • Ember •

    Vue.js • Cycle.js • React @Jack_Franklin, bit.ly/elm-polyconf 18
  3. View functions are pure view(state1) => HTML1 view(state1) => HTML1

    view(state2) => HTML2 @Jack_Franklin, bit.ly/elm-polyconf 23
  4. 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
  5. Have update functions that can handle actions update(action, state) =>

    newState @Jack_Franklin, bit.ly/elm-polyconf 25
  6. Unidirectional Data Flow user clicks -> action -> update(action, state)

    -> view(newState) @Jack_Franklin, bit.ly/elm-polyconf 27
  7. Magic! function setNewUser(name) { $scope.user = { name : 'jack'

    }; } @Jack_Franklin, bit.ly/elm-polyconf 31
  8. Explicit! function update(action, state) { switch (action.type) { case 'NEW_USER':

    return Object.assign({}, state, { user: { name: action.name } }); } } @Jack_Franklin, bit.ly/elm-polyconf 32
  9. Even more explicit! type Msg = NewUser String | LogOut

    @Jack_Franklin, bit.ly/elm-polyconf 33
  10. 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
  11. Functional Programming add(1, 2) (add 1 2) List.map (\x ->

    x + 2) [1, 2, 3, 4] List.map ((+) 2) [1, 2, 3, 4] @Jack_Franklin, bit.ly/elm-polyconf 46
  12. Pipes incrementWeight (incrementHeight (incrementAge (makePerson "jack"))) makePerson "jack" |> incrementAge

    |> incrementHeight |> incrementWeight @Jack_Franklin, bit.ly/elm-polyconf 47
  13. Clean syntax incrementAge person = { person | age =

    person.age + 1 } add x y = x + y addTwo = add 2 @Jack_Franklin, bit.ly/elm-polyconf 48
  14. Types add : Int -> Int -> Int isEven :

    Int -> Bool @Jack_Franklin, bit.ly/elm-polyconf 50
  15. 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
  16. 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
  17. 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
  18. ! 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
  19. type alias Person = { name : String , age

    : Int } @Jack_Franklin, bit.ly/elm-polyconf 55
  20. 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
  21. 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
  22. Modules ! Everything is scoped ! Modules explicitly declare what

    they expose ! Modules explicitly declare what they import @Jack_Franklin, bit.ly/elm-polyconf 59
  23. Maybe type Maybe a = Just a | Nothing It's

    either Just some value, or Nothing. @Jack_Franklin, bit.ly/elm-polyconf 61
  24. 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
  25. You must handle all cases of missing / pending data

    @Jack_Franklin, bit.ly/elm-polyconf 63
  26. 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
  27. You have to deal with errors. Task doesn't let you

    not. (We'll come back to this later). @Jack_Franklin, bit.ly/elm-polyconf 65
  28. 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
  29. 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
  30. 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
  31. 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
  32. The three parts: model : Model view : Model ->

    Html Msg update : Msg -> Model -> Model @Jack_Franklin, bit.ly/elm-polyconf 71
  33. First, define your Model type alias Model = Int initialModel

    : Model initialModel = 0 @Jack_Franklin, bit.ly/elm-polyconf 74
  34. Secondly, define your Msgs type Msg = Increment | Decrement

    @Jack_Franklin, bit.ly/elm-polyconf 75
  35. 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
  36. Fourthly, define your view: view : Model -> Html Msg

    view model = div [] [ button [ onClick Decrement ] [ text "-" ] , div [] [ text (toString model) ] , button [ onClick Increment ] [ text "+" ] ] @Jack_Franklin, bit.ly/elm-polyconf 77
  37. Finally, hook it all up! main = Html.App.beginnerProgram { model

    = initialModel , view = view , update = update } @Jack_Franklin, bit.ly/elm-polyconf 78
  38. ! 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
  39. Explicitly model side effects. Hand off to Elm, it will

    hand back later. @Jack_Franklin, bit.ly/elm-polyconf 82
  40. 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
  41. model : Model view : Model -> Html Msg update

    : Msg -> Model -> (Model, Cmd Msg) @Jack_Franklin, bit.ly/elm-polyconf 85
  42. 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
  43. Secondly, define your Msgs type Msg = NewGithubData GithubPerson |

    FetchGithubData | FetchError Http.Error @Jack_Franklin, bit.ly/elm-polyconf 89
  44. 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
  45. 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
  46. 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
  47. Fifthly (new step), define your init: initialModel : Model initialModel

    = { username = "jackfranklin" , githubPerson = Nothing } init : ( Model, Cmd Msg ) init = ( initialModel, Cmd.none ) @Jack_Franklin, bit.ly/elm-polyconf 94
  48. Finally, hook it all together! main = Html.App.program { init

    = init , view = view , update = update , subscriptions = \_ -> Sub.none } @Jack_Franklin, bit.ly/elm-polyconf 95
  49. That feels like a lot of code / effort! --

    All of you. @Jack_Franklin, bit.ly/elm-polyconf 103
  50. 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
  51. elm reactor Easily run a project in the browser with

    no tooling required. @Jack_Franklin, bit.ly/elm-polyconf 111
  52. 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
  53. You're fed up of undefined function errors that take up

    loads of time @Jack_Franklin, bit.ly/elm-polyconf 119
  54. You're fed up of packages on npm breaking semantic versioning

    @Jack_Franklin, bit.ly/elm-polyconf 120
  55. You want to develop with the confidence of Types and

    a clever compiler to back you up @Jack_Franklin, bit.ly/elm-polyconf 121
  56. You're happy to "ride the wave" and deal with a

    language still growing and settling down @Jack_Franklin, bit.ly/elm-polyconf 122
  57. You're happy to build more packages than depend on existing

    solutions which may not exist in Elm @Jack_Franklin, bit.ly/elm-polyconf 123
  58. But what if this talk has put me off Elm?

    @Jack_Franklin, bit.ly/elm-polyconf 124
  59. Elm does take time to learn, so please don't give

    up after 30 minutes of slides! guide.elm-lang.org @Jack_Franklin, bit.ly/elm-polyconf 125
  60. 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