Save 37% off PRO during our Black Friday Sale! »

Let's create a type system in plain javascript — nCraft 2017

Let's create a type system in plain javascript — nCraft 2017

Beb422437c1dfb5366f197919e41ac50?s=128

Arnaud LEMAIRE
PRO

May 19, 2017
Tweet

Transcript

  1. A type system in plain javascript @lilobase www.arpinum.fr My twitter

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

    ").length;
  3. A function is defined by its domain & co-domain function

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

    Integer count_word
  5. A function’s domain & codomain are sets String Integer count_word

    Set of all Strings Set of all Integers
  6. Knowing about domain & co-domain leads to a massive advantage

    Check correctness Int count_word
  7. Enables safe composition f g h h = g•f Knowing

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

    » Knowing about domain & co-domain leads to a massive advantage
  9. Types can be considered as « Sets »

  10. How do we check types ? const count_word = string

    => { if(typeof string !== 'string') 
 throw new TypeError;
 return string.split(" ").length; }
  11. How can we define a set ? using a predicate

    : 
 - is x a member of E
  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; }
  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; }
  14. How can we create a set ? using a constructor

    : - put x in E if x can be a member of E
  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));
  16. Type refinement Integer count_word

  17. Type refinement Positive Integer count_word

  18. Type refinement Positive Integer Integer

  19. Type refinement const Int = value => { assert(e =>

    e === parseInt(e, 10) && !isNaN(e) )(value); return value; }
  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)
 );
  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);
  22. What’s the type ? const count_each_word = string => 


    string.split(" ").reduce( (acc, item) => { acc[item] = (acc[item] || 0) + 1; return acc; } , {});
  23. What’s the the type ? String ? count_each_word

  24. Type combinator String count_each_word Dictionary : 
 String -> Int

  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 });
  26. You don’t need to code it yourself tcomb github.com/gcanti/tcomb/

  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');
  28. Type union const Ingredient = t.union([
 Vegetable, 
 Meat, 


    SeaFood ], 'Ingredient');
  29. Struct (& list) const Ingredient = t.union([
 Vegetable, 
 Meat,

    
 SeaFood ], 'Ingredient');
 
 const Kebab = t.struct({ ingredients: t.list(Ingredient), }, 'Kebab');
  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
  31. Sub-typing (refinement) const isVegetarian = t.func([Kebab], t.Boolean).of(
 ({ingredients}) => ingredients.every(Vegetable.is)

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

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

  34. And many other • Dictionary • Tuple • Recursive types

    • Maybe • Intersection • Interface • …
  35. And, they are available at runtime…

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

    
 fromJSON(data, VegetarianKebab));
  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
  38. Forms (with react) <t.form.Form ref={form => this.form = form} type={Kebab}

    value={values} />
  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
  40. Thanks ! @lilobase www.arpinum.fr If you want to work with

    us