Wednesday, November 8, 2017

Smalltalk's collection iterators implemented with ES6 arrow-functions

  const not     = f => a => ! f(a);

  const collect = f => a     => (a).map    (f);
  const select  = f => a     => (a).filter (f);
  const reject  = f => a     => (a).filter (not(f));
  const detect  = f => a     => (a).find   (f);
  const inject  = f => (a,i) => (a).reduce (f,i);

  let double   = collect (x => x * 2);
  let odd      = select  (x => x % 2);
  let even     = reject  (x => x % 2);
  let firstOdd = detect  (x => x % 2);
  let addAll   = inject  ((x, s) => s + x);

  let numbs   = [2, 4, 3, 1, 5];
  let doubles = double   (numbs);    // -> [4, 8, 6, 2, 10]
  let odds    = odd      (numbs);    // -> [3, 1, 5]
  let evens   = even    (numbs);    // -> [2, 4]
  let three   = firstOdd (numbs);    // -> 3
  let sum     = addAll   (numbs, 0); // -> 15


The equivalents of Smalltalk's  collect:, select: reject:, detect: and inject: can be implemented simply with the help of ES6 arrow-function as above.

Except this is not quite the same thing, and may help to clarify the difference between Object-Oriented  and Functional Programming. In Smalltalk #select: etc. are methods of collection classes which can be and are re-defined for several Collection subclasses in the standard library.  You call these methods upon collection instances. What the above "functional style" code does instead is create free-standing functions select() etc.  which when called with a function return another function, which then can be called with an Array instance.

Once you have the definitions for select() etc. above the code becomes about as simple as it would be with the O-O approach of having reusable  predefined methods like #select: etc. in the standard library.  Using Smalltalk's collection primitives you would in fact also be creating "throw-away functions" as arguments of those methods. Such arguments are instances of "BlockClosures" which is what functions are called in Smalltalk.

So what's the point of implementing the above when map() filter() reduce() and find() already exist in JavaScript? Well first I think there is value in naming all of these operations somewhat similarly, to make them easy to remember and to make it clear they really are part of the same group. Collect, select, reject, detect and inject  all rhyme with each other, they are all different ways of iterating over arrays.

Secondly the above definitions do not simply duplicate the behavior of map() etc. The functions collect() etc. are not methods of Array. They are functions which take a function as argument and return another function - which can then be called with an array argument to iterate over it with the specific behavior given by the argument-function of collect() etc. This gives you a bit more reuse, you can call functions like 'even()'  from many places without having to duplicate the original argument  that was given to 'reject()' every time you need this behavior.


Copyright © 2017 Panu Viljamaa. All rights reserved