A good Reason for typing

A good Reason for typing

New version of the talk I gave at Rennes.js on 05/04/2018

37500337ba5d2aebc962959ed83928e5?s=128

Matthias Le Brun

April 05, 2018
Tweet

Transcript

  1. A good Reason for typing

  2. Matthias Le Brun @bloodyowl Be

  3. <script> window.beOpinionAsyncInit = function() { BeOpinionSDK.init({ account: 'YOUR_ACCOUNT_ID', }); BeOpinionSDK.watch();

    }; !</script> <script async src="https:#//widget.beopinion.com/sdk.js%">!</script> <div class="BeOpinionWidget%">!</div>
  4. None
  5. JavaScript JS

  6. WHAT'S COOL ABOUT JS • Available everywhere • Easy to

    start • Multiparadigm
  7. WHAT'S LESS COOL ABOUT JS • Super hard to master

    • Error prone • Hacky
  8. WHAT'S LESS COOL ABOUT JS let add = (a, b)

    '=> a + b;
  9. WHAT'S LESS COOL ABOUT JS let add = (a, b)

    '=> a + b; What's its signature?
  10. WHAT'S LESS COOL ABOUT JS add(1, 1); #// 2 add(1,

    "1"); #// "11" add(1, null); #// 1 add(1, undefined); #// NaN
  11. Dynamic typing + Automatic coercion WHAT'S LESS COOL ABOUT JS

  12. WHAT'S LESS COOL ABOUT JS

  13. How do we make these bugs disappear?

  14. • ESLint? • Flow? • TypeScript?

  15. None
  16. JavaScript

  17. As developers, we make mistakes

  18. Let's not be overconfident in what we write

  19. Then how do we prevent most mistakes?

  20. Tests?

  21. Sure, tests are good and necessary, but they inherit your

    own biases
  22. Just make these mistakes impossible

  23. Reason RE

  24. What's Reason?

  25. 1. A language 2. A syntax 3. A compiler 4.

    An ecosystem
  26. OCaml

  27. WHAT'S COOL ABOUT OCAML • Has a great type system

    • Infers most types • Multiparadigm
  28. WHAT'S LESS COOL ABOUT OCAML • Syntax can be hard

    • Tooling can be hard
  29. WHAT'S LESS COOL ABOUT OCAML let rec map f =

    function [] !-> [] | a*::l !-> let r = f a in r *:: map f l
  30. 1. A language 2. A syntax 3. A compiler 4.

    An ecosystem
  31. ~ES2015+ JS

  32. A FAMILIAR SYNTAX let add a b = a +

    b let add = (a, b) '=> a + b; ML RE
  33. 1. A language 2. A syntax 3. A compiler 4.

    An ecosystem
  34. BuckleScript

  35. BUCKLESCRIPT RE JS

  36. BUCKLESCRIPT • Outputs clean and performant code • Makes interop

    easy
  37. 1. A language 2. A syntax 3. A compiler 4.

    An ecosystem
  38. npm

  39. A bit of practice?

  40. 1. Basics 2. Modules 3. Functions 4. Variants 5. Interop

  41. BASICS 1; ,/* int .*/ 1.0; ,/* float .*/ "Hello";

    ,/* string .*/ 'H'; ,/* char .*/ true; ,/* bool .*/ [1, 2, 3]; ,/* list(int) .*/ [|1, 2, 3|]; ,/* array(int) .*/ Some(x); ,/* option('a) .*/ () ,/* unit .*/
  42. RECORDS let me = { name: "Matthias", age: 24, };

    let meLater = { **...me, age: 25, }; type user = { name: string, age: int, }; You declare the type of the record You can create records of this type You can create spread values to create a new record from a previous one
  43. 1. Basics 2. Modules 3. Functions 4. Variants 5. Interop

  44. App.re MODULES Every file is a module and filenames are

    unique Welcome.re let sayHi = (name) '=> Js.log("Hi " .++ name .++ "!"); Welcome.sayHi("people"); Every top level binding is exported by default To open a module, just use its capitalised name
  45. MODULES module Talk { include Presentation; let slides = ["a",

    "b", "c"]; let slidesWithIndex = slides 2|> List.mapi((index, item) '=> (index, item)); }; open OtherModule; Declare a module within a module Include modules like a static extends Make all module available in scope
  46. 1. Basics 2. Modules 3. Functions 4. Variants 5. Interop

  47. FUNCTIONS let add = (a, b) '=> a + b;

    let addDoubles = (a, b) '=> { let doubleA = a * 2; let doubleB = b * 2; doubleA + doubleB; }; Every function has its signature let add: (int, int) '=> int = <fun>; Last expression is the returned value
  48. FUNCTIONS [1, 2, 3] 2|> List.filter(item '=> item mod 2

    **=== 0) 2|> List.map(item '=> item * 2); Pipe operator! Functions are auto curried That means that they return a function taking the rest of the arguments as long as you didn't pass all the required ones
  49. FUNCTIONS let make = ( ~rootPath="./", ~fileExtensions=["re", "js"], ~watch=false, ~onChange=?,

    () ) '=> { ,/* **... .*/ }; Functions can have labelled arguments Their order at function application doesn't mater Optionals Default values Final non labelled argument When you have optional arguments, this tells the compiler «I have all the arguments there»
  50. 1. Basics 2. Modules 3. Functions 4. Variants 5. Interop

  51. WHAT'S WRONG WITH THIS? type state = { isLoading: false,

    hasErrored: false, data: null, };
  52. IMPOSSIBLE STATES ARE ACTUALLY POSSIBLE isLoading hasErrored data FALSE FALSE

    NULL TRUE FALSE NULL FALSE TRUE NULL TRUE TRUE NULL FALSE FALSE DATA TRUE FALSE DATA FALSE TRUE DATA TRUE TRUE DATA NotAsked Loading Errored Loaded
  53. THE SOLUTION: VARIANTS type t('a) = | NotAsked | Loading

    | Loaded('a) | Errored; A variant type is a «OR» type A type can be parametrised aka Generics Every constructor can hold data like a container type
  54. THE SOLUTION: VARIANTS switch (resource) { | NotAsked '=> ""

    | Loading '=> "Loading **..." | Loaded(value) '=> "Loaded: " .++ value | Errored '=> "An error occurred" }; You use pattern matching to extract values
  55. THE SOLUTION: VARIANTS switch (resource) { | NotAsked '=> ""

    | Loading '=> "Loading **..." | Loaded(value) '=> "Loaded: " .++ value }; Warning 8: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: Errored
  56. THERE'S NO NULL type option('a) = | Some('a) | None;

    switch (value) { | Some(value) '=> value | None '=> "Nothing to see" }; Move null checks at compile time If it's maybe a value, you need to handle it explicitly
  57. 1 ARRAY VS LIST 1 2 3 1 1 1

    2 1 3 1
  58. LIST IS A VARIANT TYPE type list('a) = | Empty

    | Head('a, list('a)); let list = Head(1, Head(2, Head(3, Empty)));
  59. LIST IS A VARIANT TYPE let rec map = (f,

    list) '=> switch (list) { | Empty '=> Empty | Head(x, rest) '=> Head(f(x), map(f, rest)) }; If there's nothing left Return empty Return a head with transformed item then transform the rest with a recursive call Recursive
  60. LIST IS A VARIANT TYPE let foldLeft = (array, f,

    acc) '=> { let index = -1; while(.++index < array.length) { acc = f(acc, array[index]) }; return acc }; Reassigned Reassigned
  61. ACTUALLY IN JS #// http:#//es5.github.io/#x15.4.4.21 function foldLeft(arr, fn) { var

    array = new Object(arr), index = -1, length = array.length 55>>> 0, acc, hasInitialValue; if (Object.prototype.toString.call(fn) 2!= "[object Function]") { throw new TypeError(); } if (arguments.length > 1) { acc = arguments[1]; } else { hasInitialValue = false; while (.++index < length) { if (!(hasInitialValue = index in array)) { continue; } acc = array[index]; break; } if (!hasInitialValue) { throw new TypeError(); } } while (.++index < length) { if (!(index in array)) continue; acc = fn.call(void 0, acc, array[index], index, array); } return acc; };
  62. ACTUALLY IN JS: DYNAMIC TYPING #// http:#//es5.github.io/#x15.4.4.21 function foldLeft(arr, fn)

    { var array = new Object(arr), index = -1, length = array.length 55>>> 0, acc, hasInitialValue; if (Object.prototype.toString.call(fn) 2!= "[object Function]") { throw new TypeError(); } if (arguments.length > 1) { acc = arguments[1]; } else { hasInitialValue = false; while (.++index < length) { if (!(hasInitialValue = index in array)) { continue; } acc = array[index]; break; } if (!hasInitialValue) { throw new TypeError(); } } while (.++index < length) { if (!(index in array)) continue; acc = fn.call(void 0, acc, array[index], index, array); } return acc; };
  63. LIST IS A VARIANT TYPE let rec fold_left = (f,

    acc, list) '=> switch (list) { | [] '=> acc | [x, **...rest] '=> fold_left(f, f(acc, x), rest) };
  64. 1. Basics 2. Modules 3. Functions 4. Variants 5. Interop

  65. How do we incrementally adopt Reason on an existing codebase?

    INTEROP
  66. INTEROP: EXTERNAL MODULES [@bs.module ".../myExistingJsModule"] external myExistingJsModule : string =

    ""; Declare that it's a module located at ".../myExistingModule" The module is of string type Js.log(myExistingJsModule); The module is only imported when used
  67. INTEROP: EXTERNAL MODULES #// Generated by BUCKLESCRIPT VERSION 2.2.4, PLEASE

    EDIT WITH CARE 'use strict'; var MyExistingJsModule = require(".../myExistingJsModule"); console.log(MyExistingJsModule.myExistingJsModule); ,/* Not a pure module .*/
  68. INTEROP: CREATE AND READ JAVASCRIPT OBJECTS let myJsObject = {

    "name": "Matthias", "age": Js.Null.return(24) }; Js.log(myJsObject*##name);
  69. INTEROP: ACCESS THE JS STDLIB array 2|> Js.Array.map(item '=> item

    * 2);
  70. ReasonReact

  71. WHERE DOES REACT COME FROM? • Immutability • Functional approach

    • PropTypes
  72. WHERE DOES REACT COME FROM? Jordan Walke wrote the first

    React prototype in SML
  73. REASONREACT HAS A LOT BAKED IN • Components • Router

    • Reducers • Subscriptions
  74. JSX <MyComponent prop optionalProps=?optionalValue boolProp=false :/>

  75. REASONREACT: BASIC COMPONENT let component = ReasonReact.statelessComponent("HelloWorld"); let make =

    (~name, _children) '=> { **...component, render: (_self) '=> <div> (ReasonReact.stringToElement("Hello " .++ name)) !</div> };
  76. REASONREACT: REDUCER COMPONENT type state = int; type action =

    | Increment | Decrement; let component = ReasonReact.reducerComponent("Counter"); Define the type of the state Define all possible actions
  77. REASONREACT: REDUCER COMPONENT reducer: (action, state) '=> switch (action) {

    | Increment '=> ReasonReact.Update(state + 1) | Decrement '=> ReasonReact.Update(state - 1) } Return an update for each action can be Update, UpdateWithSideEffect, SideEffect …
  78. REASONREACT: REDUCER COMPONENT render: ({state, send}) '=> <div> (ReasonReact.stringToElement(string_of_int(state))) <Button

    onClick=((_) '=> send(Increment)) title="+" :/> <Button onClick=((_) '=> send(Decrement)) title="-" :/> !</div> Send actions in your render function
  79. REASONREACT: INTEROP ReasonReact.wrapReasonForJs(,/* **... .*/) ReasonReact.wrapJsForReason(,/* **... .*/)

  80. Demo (If I still got time)

  81. Sum up

  82. for 10 months in production >85% of our front-end Be

    RE @ SUM UP
  83. Safer Less bugs Easy refactoring SUM UP

  84. We all come up with the wrong assumptions on our

    first pass at something SUM UP
  85. Fix your assumptions at the type level and let the

    compiler show you the way SUM UP
  86. Spaghetti Reason code is better than spaghetti JS SUM UP

  87. That's a good Reason for typing SUM UP: ONE QUESTION

  88. Thank you GOT ANY QUESTIONS? ✋ Be Matthias Le Brun

    @bloodyowl