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

JavaScript Combinators

JavaScript Combinators

This is part one of two-part talk on given at NDC Oslo 2014. It is written in Markdown and was presented on-screen using Deckset.

Most of the code from the "JavaScript Combinators" is adapted from JavaScript Allongé and from its library, allong.es. The code from "The Art of the JavaScript Metaobject Protocol" was adapted from JavaScript Spessore. The material discussed in the talks is free to read online.

F8f7496052d3bf856e944aec64cfbb99?s=128

Reg Braithwaite

June 05, 2014
Tweet

Transcript

  1. None
  2. A Unified Theory of JavaScript Style, Part I JavaScript Combinators

  3. None
  4. we'll talk about Combinators and decorators

  5. None
  6. but think about Flexibility and decluttering

  7. None
  8. composition We compose entities to create new entities

  9. None
  10. interfaces Not all entities "fit together"

  11. None
  12. Homogeneous interfaces create dense spaces

  13. None
  14. Heterogeneous interfaces create sparse spaces

  15. None
  16. Dense is more flexible than sparse

  17. None
  18. Sparse can be quicker to grasp

  19. None
  20. enough with the math!

  21. None
  22. pluck: "A convenient version of what is perhaps the most

    common use-case for map: extracting a list of property values."
  23. "pluckWith" is the flipped form of "pluck"

  24. function pluck (mappable, key) { return mappable.map(function (obj) { return

    obj[key]; }); }; function pluckWith (key, mappable) { return pluck(mappable, key); }; var stooges = [ {name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}]; pluckWith('name', stooges); //=> ["moe", "larry", "curly"]
  25. Let's make "pluckWith" out of combinators

  26. A unary combinator function flip (fn) { return function flipped

    (a, b) { return fn.call(this, b, a); } } function arrow (a, b) { return "" + a + " -> " + b; } flip(arrow)("x", "y") //=> 'y -> x'
  27. None
  28. curry Another unary combinator

  29. function curry (fn) { return function curried (a, optionalB) {

    if (arguments.length > 1) { return fn.call(this, a, optionalB); } else return function partiallyApplied (b) { return fn.call(this, a, b); } } }
  30. Currying: var curriedArrow = curry(arrow); //=> [function] curriedArrow('finger')('moon') //=> 'finger

    -> moon'
  31. Partial Application: var taoism = curry(arrow)('finger'); //=> [function] taoism('moon') //=>

    'finger -> moon'
  32. None
  33. nota bene Partial application transforms binary operations into unary operations

  34. function get (object, property) { return object[property]; } get({foo: 1},

    'foo') //=> 1
  35. var getWith = curry(flip(get)); getWith('foo')({foo: 1}) //=> 1

  36. function map (mappable, fn) { return mappable.map(fn, this); } function

    double (n) { return n * 2; } map([1, 2, 3], double) //=> [2, 4, 6]
  37. var mapWith = curry(flip(map)); mapWith(double, [1, 2, 3]); //=> [2,

    4, 6] var doubleAll = mapWith(double); doubleAll([1, 2, 3]) //=> [2, 4, 6]
  38. “almost there...”

  39. function pluckWith (attr) { return mapWith(getWith(attr)); }

  40. None
  41. Compose

  42. function compose (a, b) { return function composed (c) {

    return a(b(c)); } }
  43. quod erat demonstrandum The combinator implementation of "pluckWith"

  44. var pluckWith = compose(mapWith, getWith);

  45. Let's compare both implementations of "pluckWith"

  46. var pluckWith = compose(mapWith, getWith); //// versus //// function pluck

    (mappable, key) { return mappable.map(function (obj) { return obj[key]; }); }; function pluckWith (key, mappable) { return pluck(mappable, key); };
  47. lesson Composing functions with combinators increases code flexibility...

  48. lesson Composing functions with combinators demands increased mental flexibility

  49. using combinators to make Decorators

  50. function Cake () {} extend(Cake.prototype, { mix: function () {

    // mix ingredients together return this; }, rise: function (duration) { // let the ingredients rise return this; }, bake: function () { // do some baking return this; } });
  51. fluent function fluent (methodBody) { return function fluentized () {

    methodBody.apply(this, arguments); return this; } }
  52. function Cake () {} extend(Cake.prototype, { mix: fluent( function ()

    { // mix ingredients together }), rise: fluent( function (duration) { // let the ingredients rise }), bake: fluent(function () { // do some baking }) });
  53. new requirements Mix before rising or baking

  54. extend(Cake.prototype, { mix: fluent( function () { // mix ingredients

    together }), rise: fluent( function (duration) { this.mix(); // let the ingredients rise }), bake: fluent(function () { this.mix(); // do some baking }) });
  55. before a combinator that transforms decorations into decorators var before

    = curry( function decorate (decoration, method) { return function decoratedWithBefore () { decoration.apply(this, arguments); return method.apply(this, arguments); }; } ); var mixFirst = before(function () { this.mix() });
  56. the final version extend(Cake.prototype, { // Other methods... mix: fluent(

    function () { // mix ingredients together }), rise: fluent( mixFirst( function (duration) { // let the ingredients rise })), bake: fluent( mixFirst( function () { // do some baking })) });
  57. lesson Decorators declutter secondary concerns

  58. after var after = curry( function decorate (decoration, method) {

    return function decoratedWithAfter () { var returnValue = method.apply(this, arguments); decoration.apply(this, arguments); return returnValue; }; } );
  59. around var around = curry( function decorate (decoration, method) {

    return function decoratedWithAround () { var methodPrepended = [method].concat( [].slice.call(arguments, 0) ); return decoration.apply(this, methodPrepended); }; } );
  60. call me maybe var maybe = around(function (fn, value) {

    if (value != null) { return fn.call(this, value);; } }); maybe(double)(2) //=> 4 maybe(double)(null) //=> undefined
  61. generalized guards function provided (guard) { return around(function () {

    var fn = arguments[0], values = [].slice.call(arguments, 1); if (guard.apply(this, values)) { return fn.apply(this, values); } }); } var maybe = provided( function (value) { return value != null; });
  62. inversions function not (fn) { return function notted () {

    return !fn.apply(this, arguments) } } var except = compose(provided, not); var maybe = except( function (value) { return value == null; });
  63. None
  64. lessons

  65. None
  66. lesson one Combinators increase code flexibility and require increased mental

    flexibility
  67. None
  68. lesson two Decorators declutter secondary concerns

  69. None
  70. lesson three Do not follow in the footsteps of the

    sages. Seek what they sought
  71. Reginald Braithwaite GitHub, Inc. raganwald.com @raganwald NDC Conference, Oslo, Norway,

    June 5, 2014