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

Créons un système de type en Javascript – SunnyTech 2018

Créons un système de type en Javascript – SunnyTech 2018

Qu'est-ce qu'un type ? Comment fonctionne un système de type ?

Essayons de répondre brièvement à ces deux questions via cette rapide présentation où nous allons construire un petit système de type à partir de zéro et dans un langage qui n'est pas connu pour ses types : JavaScript.

Arnaud LEMAIRE
PRO

June 29, 2018
Tweet

More Decks by Arnaud LEMAIRE

Other Decks in Programming

Transcript

  1. A type system in plain
    javascript
    @lilobase lgo.group
    My twitter

    View Slide

  2. What’s the type ?
    const count_word =
    string => string.split(" ").length;

    View Slide

  3. A function is defined by its
    domain & co-domain
    function
    Domain
    Co-
    Domain

    View Slide

  4. A function is defined by its
    domain & co-domain
    String Integer
    count_word

    View Slide

  5. A function’s domain &
    codomain are sets
    String Integer
    count_word
    Set of all Strings Set of all Integers

    View Slide

  6. Knowing about domain & co-domain
    leads to a massive advantage
    Check correctness
    Int count_word

    View Slide

  7. Enables safe composition
    f g
    h
    h = g•f
    Knowing about domain & co-domain
    leads to a massive advantage

    View Slide

  8. Adapts behavior
    f
    { }
    This is « pattern matching »
    Knowing about domain & co-domain
    leads to a massive advantage

    View Slide

  9. Types can be considered as
    « Sets »

    View Slide

  10. How do we check types ?
    const count_word = string => {
    if(typeof string !== 'string') 

    throw new TypeError;

    return string.split(" ").length;
    }

    View Slide

  11. How can we define a set ?
    using a predicate : 

    - is x a member of E

    View Slide

  12. How do we check types ?
    const assert = predicate => value => {
    if(!predicate(value)) throw new TypeError;
    }
    const count_word = string => {
    assert(e => typeof e === 'string')(string);
    return string.split(" ").length;
    }

    View Slide

  13. How do we check types ?
    const assert = predicate => value => {
    if(!predicate(value)) throw new TypeError;
    }
    const Str = assert(e => typeof e === 'string');
    const count_word = string => {
    Str(string);
    return string.split(" ").length;
    }

    View Slide

  14. How can we create a set ?
    using a constructor :
    - put x in E if x can be a member of E

    View Slide

  15. How do we check types ?
    const assert = predicate => value => {
    if(!predicate(value)) throw new TypeError;
    }
    const Str = value => {
    assert(e => typeof e === 'string')(value);
    return value;
    };
    const count_word = 

    string => string.split(" ").length;
    const typed_count_word = 

    value => count_word(Str(value));

    View Slide

  16. Type refinement
    Integer
    count_word

    View Slide

  17. Type refinement
    Positive
    Integer
    count_word

    View Slide

  18. Type refinement
    Positive
    Integer
    Integer

    View Slide

  19. Type refinement
    const Int = value => {
    assert(e =>
    e === parseInt(e, 10) && !isNaN(e)
    )(value);
    return value;
    }

    View Slide

  20. Let’s create a type
    constructor
    const Type = predicate => value => {
    assert(predicate)(value);
    return value;
    }
    const Int = Type(

    e => e === parseInt(e, 10) && !isNaN(e)

    );

    View Slide

  21. Type refinement
    const Int = Type(

    e => e === parseInt(e, 10) && !isNaN(e)

    );
    const refinement = (type, predicate) => value =>

    Type(predicate)(type(value));
    const PositiveInt = refinement(Int, e => e > 0);

    View Slide

  22. What’s the type ?
    const count_each_word = string => 

    string.split(" ").reduce(
    (acc, item) => {
    acc[item] = (acc[item] || 0) + 1;
    return acc;
    }
    , {});

    View Slide

  23. What’s the the type ?
    String ?
    count_each_word

    View Slide

  24. Type combinator
    String count_each_word
    Dictionary : 

    String -> Int

    View Slide

  25. Type combinator
    const dict = (domain, codomain) => values => {
    Object.entries(values)

    .forEach(([key, value]) => {
    domain(key);
    codomain(value);
    });
    return values;
    };
    const Phonebook = dict(Str, Int);
    const phonebook = Phonebook({
    'Andrew Parson': 8806336,
    'Emily Everett': 6784346
    });

    View Slide

  26. You don’t need to code it
    yourself
    tcomb
    github.com/gcanti/tcomb/

    View Slide

  27. Enums
    const Vegetable = t.enums.of([
    'Tomato',
    'Onion',
    'Salad'
    ], 'Vegetable');
    const Meat = t.enums.of([
    'Mutton'
    ], 'Meat');
    const SeaFood = t.enums.of([
    'Shrimp',
    'Fish'
    ], 'SeaFood');

    View Slide

  28. Type union
    const Ingredient = t.union([

    Vegetable, 

    Meat, 

    SeaFood
    ], 'Ingredient');

    View Slide

  29. Struct (& list)
    const Ingredient = t.union([

    Vegetable, 

    Meat, 

    SeaFood
    ], 'Ingredient');


    const Kebab = t.struct({
    ingredients: t.list(Ingredient),
    }, 'Kebab');

    View Slide

  30. Typed function
    const Vegetable = t.enums.of([
    'Tomato',
    'Onion',
    'Salad'
    ], 'Vegetable');
    const Kebab = t.struct({
    ingredients: t.list(Ingredient),
    }, 'Kebab');

    const isVegetarian = t.func([Kebab], t.Boolean).of(

    ({ingredients}) => ingredients.every(Vegetable.is)
    );
    Type definition
    Our function

    View Slide

  31. Sub-typing (refinement)
    const isVegetarian = t.func([Kebab], t.Boolean).of(

    ({ingredients}) => ingredients.every(Vegetable.is)
    );

    const VeggieKebab =
    t.refinement(Kebab, isVegetarian, 'VegetarianKebab');

    View Slide

  32. Is my kebab vegetarian ?
    const kebab = Kebab(['Tomato', 'Salad']);
    VegetarianKebab.is(kebab);

    View Slide

  33. Type safety
    const veggiKebab = VegetarianKebab([
    'Tomato',
    'Salad'
    ]);

    View Slide

  34. And many other
    • Dictionary
    • Tuple
    • Recursive types
    • Maybe
    • Intersection
    • Interface
    • …

    View Slide

  35. And, they are available
    at runtime…

    View Slide

  36. Payload validation
    import fromJSON from 'tcomb/lib/fromJSON';
    axios
    .get('/api/kebab/veggie/5')
    .then(({data}) => 

    fromJSON(data, VegetarianKebab));

    View Slide

  37. Pattern matching
    const result = t.match(myCurrentKebab,
    //do something if the kebab is vegetarian
    VegetarianKebab, (kebab) => ... ,
    //do something else for all other kebab
    Kebab, (kebab) => ... ,
    //this is definitely not a kebab
    t.Any, (x) => ...
    );
    The value to match against
    Type to match
    Function to execute if
    the associated type is
    matched

    View Slide

  38. Forms (with react)
    ref={form => this.form = form}
    type={Kebab}
    value={values}
    />

    View Slide

  39. Take away
    • Types are not data nor behavior

    they can be seen as sets
    • There is a set algebra we can use to combine types

    (algebraic typing)
    • Use tComb 

    (we do it, everyday, on all production projets)
    • And, moreover, it enables a better domain
    representation

    View Slide

  40. Thanks !
    @lilobase lgo.group
    a fair, secure by design &
    transparent exchange

    View Slide