$30 off During Our Annual Pro Sale. View Details »

A good Reason for typing

A good Reason for typing

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

Matthias Le Brun

April 05, 2018
Tweet

More Decks by Matthias Le Brun

Other Decks in Programming

Transcript

  1. A good Reason
    for typing

    View Slide

  2. Matthias Le Brun
    @bloodyowl
    Be

    View Slide

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

    View Slide

  4. View Slide

  5. JavaScript
    JS

    View Slide

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

    View Slide

  7. WHAT'S LESS COOL ABOUT JS
    • Super hard to master
    • Error prone
    • Hacky

    View Slide

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

    View Slide

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

    View Slide

  10. WHAT'S LESS COOL ABOUT JS
    add(1, 1); #// 2
    add(1, "1"); #// "11"
    add(1, null); #// 1
    add(1, undefined); #// NaN

    View Slide

  11. Dynamic typing
    + Automatic coercion
    WHAT'S LESS COOL ABOUT JS

    View Slide

  12. WHAT'S LESS COOL ABOUT JS

    View Slide

  13. How do we make
    these bugs disappear?

    View Slide

  14. • ESLint?
    • Flow?
    • TypeScript?

    View Slide

  15. View Slide

  16. JavaScript

    View Slide

  17. As developers, we
    make mistakes

    View Slide

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

    View Slide

  19. Then how do we
    prevent most
    mistakes?

    View Slide

  20. Tests?

    View Slide

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

    View Slide

  22. Just make these
    mistakes impossible

    View Slide

  23. Reason
    RE

    View Slide

  24. What's Reason?

    View Slide

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

    View Slide


  26. OCaml

    View Slide

  27. WHAT'S COOL ABOUT OCAML
    • Has a great type system
    • Infers most types
    • Multiparadigm

    View Slide

  28. WHAT'S LESS COOL ABOUT OCAML
    • Syntax can be hard
    • Tooling can be hard

    View Slide

  29. WHAT'S LESS COOL ABOUT OCAML
    let rec map f = function
    [] !-> []
    | a*::l !-> let r = f a in r *:: map f l

    View Slide

  30. 1. A language
    2. A syntax
    3. A compiler
    4. An ecosystem

    View Slide

  31. ~ES2015+
    JS

    View Slide

  32. A FAMILIAR SYNTAX
    let add a b = a + b
    let add = (a, b) '=> a + b;
    ML
    RE

    View Slide

  33. 1. A language
    2. A syntax
    3. A compiler
    4. An ecosystem

    View Slide

  34. BuckleScript

    View Slide

  35. BUCKLESCRIPT
    RE
    JS

    View Slide

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

    View Slide

  37. 1. A language
    2. A syntax
    3. A compiler
    4. An ecosystem

    View Slide

  38. npm

    View Slide

  39. A bit of practice?

    View Slide

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

    View Slide

  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 .*/

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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 = ;
    Last expression is the
    returned value

    View Slide

  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

    View Slide

  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»

    View Slide

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

    View Slide

  51. WHAT'S WRONG WITH THIS?
    type state = {
    isLoading: false,
    hasErrored: false,
    data: null,
    };

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  57. 1
    ARRAY VS LIST
    1 2 3
    1
    1 1
    2 1
    3 1

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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;
    };

    View Slide

  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;
    };

    View Slide

  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)
    };

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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 .*/

    View Slide

  68. INTEROP: CREATE AND READ JAVASCRIPT OBJECTS
    let myJsObject = {
    "name": "Matthias",
    "age": Js.Null.return(24)
    };
    Js.log(myJsObject*##name);

    View Slide

  69. INTEROP: ACCESS THE JS STDLIB
    array
    2|> Js.Array.map(item '=> item * 2);

    View Slide

  70. ReasonReact

    View Slide

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

    View Slide

  72. WHERE DOES REACT COME FROM?
    Jordan Walke wrote the first
    React prototype in SML

    View Slide

  73. REASONREACT HAS A LOT BAKED IN
    • Components
    • Router
    • Reducers
    • Subscriptions

    View Slide

  74. JSX
    prop
    optionalProps=?optionalValue
    boolProp=false
    :/>

    View Slide

  75. REASONREACT: BASIC COMPONENT
    let component = ReasonReact.statelessComponent("HelloWorld");
    let make = (~name, _children) '=> {
    **...component,
    render: (_self) '=>

    (ReasonReact.stringToElement("Hello " .++ name))
    !
    };

    View Slide

  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

    View Slide

  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 …

    View Slide

  78. REASONREACT: REDUCER COMPONENT
    render: ({state, send}) '=>

    (ReasonReact.stringToElement(string_of_int(state)))
    send(Increment)) title="+" :/>
    send(Decrement)) title="-" :/>
    !
    Send actions in your render function

    View Slide

  79. REASONREACT: INTEROP
    ReasonReact.wrapReasonForJs(,/* **... .*/)
    ReasonReact.wrapJsForReason(,/* **... .*/)

    View Slide

  80. Demo
    (If I still got time)

    View Slide

  81. Sum up

    View Slide

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

    View Slide

  83. Safer
    Less bugs
    Easy refactoring
    SUM UP

    View Slide

  84. We all come up
    with the wrong
    assumptions on
    our first pass at
    something
    SUM UP

    View Slide

  85. Fix your
    assumptions at the
    type level and let
    the compiler show
    you the way
    SUM UP

    View Slide

  86. Spaghetti Reason
    code is better
    than spaghetti JS
    SUM UP

    View Slide

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

    View Slide

  88. Thank you
    GOT ANY QUESTIONS?

    Be
    Matthias Le Brun
    @bloodyowl

    View Slide