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

FullStackFest: Elm for JS Developers

Aea964cf59c0c81fff752896f070cbbb?s=47 Jack Franklin
September 08, 2016

FullStackFest: Elm for JS Developers

Aea964cf59c0c81fff752896f070cbbb?s=128

Jack Franklin

September 08, 2016
Tweet

Transcript

  1. None
  2. !

  3. None
  4. None
  5. None
  6. It's a sign!

  7. @Jack_Franklin

  8. None
  9. None
  10. None
  11. "Let's replicate rails"

  12. MVC / MVVC / MCVCVMMCVCCC

  13. Two way data binding and dirty checking

  14. Object.observe

  15. We can do better

  16. None
  17. None
  18. Changes in data

  19. The application owns its state

  20. Single source of truth

  21. http://todo.com/filter/completed { todos: [{ ... }, { ... }], filter:

    'completed' } Duplicate knowledge = out of sync quickly!
  22. Most state lives in one place, with exceptions.

  23. It Depends

  24. Our UI becomes a representation of state at a given

    time
  25. { todos: [{ text: 'Do an Elm talk', done: true

    }, ... ] }
  26. { todos: [{ text: 'Do an Elm talk', done: false

    }, ... ] }
  27. { todos: [{ text: 'Do an Elm talk', done: true

    }, ... ] }
  28. Efficient rendering is not a developer concern1 1 within reason,

    not always, be reasonable, thanks!
  29. Explicitly define how the user can modify the state

  30. And be able to trace it

  31. angular.controller('MyCtrl', function($scope) { $scope.onClick = function() { $scope.x = $scope.x

    + 1; } function fetchDataAndIncrement() { fetch('api.com/data').then(function(data) { $scope.data = data; $scope.x = $scope.x + 1; }); } });
  32. function addTodo() { return { type: 'USER_ADD_TODO', text: 'Buy Milk'

    } } These can be logged, stored, tracked, serialized and so on.
  33. Have update functions that can handle actions and update the

    state update(action, state) => newState
  34. const action = { type: 'USER_LOG_OUT'; }; const currentState =

    { user: { name: 'Jack' } }; const newState = update(action, currentState); newState => { user: undefined };
  35. update encapsulates most business logic

  36. Your logic and your UI are separated

  37. Data flow

  38. None
  39. Recommended Reading ➡ Unidirectional User Interface Architectures by André Staltz

  40. None
  41. 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 } }); } }
  42. Even better! type Msg = NewUser String | LogOut

  43. None
  44. Elm: a language to solve these problems.

  45. Not the perfect language (yet?!)

  46. Not the perfect solution to all our problems

  47. → Functional → Typed → Compiled

  48. → Expressive, clear code → Self documenting → Robust

  49. Learning curve ahead!

  50. Expressive, clear code (add 1 2) List.map (\x -> x

    + 2) [1, 2, 3, 4] List.map ((+) 2) [1, 2, 3, 4]
  51. Pipes incrementWeight (incrementHeight (incrementAge (makePerson "jack"))) makePerson "jack" |> incrementAge

    |> incrementHeight |> incrementWeight
  52. ! makePerson "jack" |> incrementAge |> incrementHeight |> incrementWeight |>

    incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight |> incrementWeight
  53. incrementAge person = { person | age = person.age +

    1 } add x y = x + y addTwo = add 2
  54. Self documenting

  55. Types add : Int -> Int -> Int add x

    y = x + y isEven : Int -> Bool isEven x = n % 2 == 0
  56. Union Types type Filter = ShowAll | ShowCompleted | ShowActive

  57. 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
  58. 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!
  59. Type aliases type alias Person = { name : String

    , age : Int } incrementAge : Person -> Person incrementAge person = { person | person.age = person.age + 1 }
  60. ! Clearer code, typed in your domain specific objects. !

    Compiler can guarantee you're meeting the type requirements. ! No more 'undefined is not a function' !
  61. Robust

  62. Immutability brings guarantees var person = { name: 'Jack', age:

    24 }; incrementAge(person); // has this mutated? // does it return a new person?
  63. None
  64. 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
  65. 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
  66. Dealing with nothing

  67. I call it my billion-dollar mistake.

  68. It was the invention of the null reference in 1965.

  69. This has led to innumerable errors, vulnerabilities, and system crashes

    -- Tony Hoare
  70. You might say it's a...

  71. ...hoare-able mistake!

  72. (You may leave)

  73. No null: how do you represent a value being present

    or empty?
  74. render() { return <p>{ this.props.apiData.username }</p>; }

  75. Maybe

  76. Maybe type Maybe a = Just a | Nothing It's

    either Just some value, or Nothing.
  77. As a value, I am: → Just the integer 5

    → or, I am Nothing I have the type Maybe Int
  78. 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
  79. 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) ]
  80. You must handle all cases of missing / pending data

  81. 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
  82. Commands an async thing that Elm should run for you

    (HTTP requests)
  83. Learning curve!

  84. 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
  85. None
  86. None
  87. Building actual apps in Elm

  88. The Elm Architecture

  89. The three parts: model : Model view : Model ->

    Html Msg update : Msg -> Model -> Model
  90. What changed?

  91. None
  92. Building a counter app

  93. First, define your Model type alias Model = Int initialModel

    : Model initialModel = 0
  94. Secondly, define your Msgs type Msg = Increment | Decrement

  95. Thirdly, define your update: update : Msg -> Model ->

    Model update msg model = case msg of Increment -> model + 1 Decrement -> model - 1
  96. Fourthly, define your view: view : Model -> Html Msg

    view model = div [] [ button [ onClick Decrement ] [ text "-" ] , div [] [ text (toString model) ] , button [ onClick Increment ] [ text "+" ] ]
  97. Finally, hook it all up! main = Html.App.beginnerProgram { model

    = initialModel , view = view , update = update }
  98. None
  99. ! We left the view until last. ! Explained all

    our logic before the UI. ! Notice how easy update would be to test.
  100. Side Effects

  101. Explicitly model side effects. Hand off to Elm, it will

    hand back later. Keeps functions pure, and async easier to reason about.
  102. Commands

  103. 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.
  104. The Elm Architecture, Part 2

  105. model : Model view : Model -> Html Msg update

    : Msg -> Model -> (Model, Cmd Msg)
  106. None
  107. Fetching someone's GitHub data.

  108. Firstly, define the model: type alias GithubPerson = { name

    : String , company : String } type alias Model = { username : String , githubPerson : Maybe GithubPerson }
  109. Secondly, define your Msgs type Msg = NewGithubData GithubPerson |

    FetchGithubData | FetchError Http.Error
  110. 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 ) ...
  111. NewGithubData person -> ( { model | githubPerson = Just

    person }, Cmd.none ) -- Cmd.none === do nothing
  112. FetchGithubData -> ( model, fetchGithubData model.username ) --- fetchGithubData returns

    a command --- which Elm will run for us
  113. None
  114. 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!" ] ]
  115. Fifthly (new step), define your init: initialModel : Model initialModel

    = { username = "jackfranklin" , githubPerson = Nothing } init : ( Model, Cmd Msg ) init = ( initialModel, Cmd.none )
  116. Finally, hook it all together! main = Html.App.program { init

    = init , view = view , update = update , subscriptions = \_ -> Sub.none }
  117. None
  118. None
  119. None
  120. None
  121. None
  122. None
  123. None
  124. None
  125. None
  126. None
  127. Deep breath!

  128. That feels like a lot of code to do something

    so simple -- All of you.
  129. Boilerplate vs Explicitness

  130. Benefits increase as application grows Which is often where JavaScript

    starts to struggle.
  131. jackfranklin/elm-for-js-developers-talk

  132. Components...ish?

  133. None
  134. The Elm Ecosystem

  135. elm reactor

  136. elm package

  137. None
  138. There's so much more I haven't covered.

  139. So, why / when should you use Elm?

  140. You're fed up of undefined function errors that take up

    loads of time
  141. You want to develop with the confidence of Types and

    a clever compiler to back you up
  142. You're happy to "ride the wave" and deal with a

    language still growing and settling down
  143. You're happy to build more packages than depend on existing

    solutions which may not exist in Elm
  144. But what if this talk has put me off Elm?

  145. Elm does take time to learn, so please don't give

    up after 30 minutes of slides! guide.elm-lang.org
  146. Elm the language brings many concepts that are language agnostic

  147. The Elm Architecture

  148. Explicitness across your application

  149. Types

  150. Immutability / Functional Programming

  151. 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.
  152. Will everyone be writing Elm in 1/2/5 years?

  153. It Depends

  154. But it's great fun!

  155. → guide.elm-lang.org → elm-lang.org/docs → elm-lang.org/community → github.com/jackfranklin/elm-for-js-developers- talk →

    speakerdeck.com/jackfranklin
  156. @Jack_Franklin javascriptplayground.com