

Transducers for JavaScript - dahjelle
https://github.com/cognitect-labs/transducers-js

======
gcanti
When I see concepts like transducers I'm always torn in two:

 __my theoretical side __is really excited: reducing (pun intended) a bunch of
different operations to a minimal set of basic, composable operations is an
awesome intellectual challenge. I imagine how succinct, reusable and
multipurpose could become my code.

 __my pragmatic side __is skeptic: in functional programming it 's a little
step to end up with incomprehensible code, for me and my coworkers:

    
    
        var inc = function(n) { return n + 1; };
        var isEven = function(n) { return n % 2 == 0; };
        var xf = comp(map(inc), filter(isEven));
        console.log(into([], xf, [0,1,2,3,4])); // [2,4]
    

vs

    
    
        var inc = function(n) { return n + 1; };
        var isEven = function(n) { return n % 2 == 0; };
        [0,1,2,3,4].map(inc).filter(isEven); // [2,4]
    

The latter is comprehensible by everyone (and no additional library). Ok you
iterate twice but if you really (REALLY) have performance problems, maybe
you'd end up with something like this:

    
    
        var input = [0,1,2,3,4];
        var output = [];
        var x;
        for (var i = 0, len = input.length ; i < len ; i++ ) {
          x = input[i] + 1; // map
          if (x % 2 == 0) ouput.push(x); // filter
        }

~~~
dwenzek
Yes, the former is more efficient and the latter more comprehensible. But
there is a simple step which can reconcile both approaches.

For that, we have to add some functions, so we can write :

    
    
        reduce([0,1,2,3,4]).map(inc).filter(isEven).into(array);
    

The names `reduce`, `into` and `array` may surely be improved; but they convey
the idea well enough.

The `reduce` function takes an iterable object and turns it into a reducible
object i.e. an object with an `into` method to which will be provided all the
stuff required to reduce the content of the former iterable into a new array,
a sum or whatever result which can be obtained adding items one after the
other into a seed.

Note that the `reduce` function doesn't iterate over its argument. Neither do
the `map` and `filter` methods. Along the chain the iterable is simply wrapped
with functions and filters to be used latter. All computations occure when an
actual reducer is provided through a call to the `into` method. Then the
mapping and filtering arguments are used to transform the given reducer into a
new specific reducer. (this is why they are called transducers). And then the
iterable is reduced using some loop like the one you show.

So transducers can be wrapped to be used like regular filters over
collections. The beauty of transducers is that they express efficient
transformation chains which do not depend of actual input and output. By the
way, it seems to me that the proposed javascript transducers miss the last
point : the proposed `into` function takes an implied reducer which is
computed after the input. An array is always reduced to an array ! What about
reduction into a string or a sum ?

Last remark. Bellow a reducible has to wrap a transducer and the code of
`into` has to check if there is actually a transducer (chaining mapping and
filtering). This is a bit ugly. I think this is due to an over emphasis on
transducers. The code would be simpler if we were transforming reducibles
either reducers.

\-------

    
    
        transducers.Reducible = function(coll, reduce, transducer) {
            this.coll = coll;
            this.reduce = reduce;
            this.transducer = transducer;
        };
    
        transducers.Reducible.prototype.into = function(xf) {
            if (this.transducer == null) {
               return this.reduce(xf, xf.init(), this.coll);
            } else {
               transduced_xf = this.transducer(xf);
               return this.reduce(transduced_xf, transduced_xf.init(), this.coll);
            }
        };
    
        transducers.Reducible.prototype.comp = function(other_transducer) {
            if (this.transducer == null) {
               return other_transducer;
            } else {
               return transducers.comp(this.transducer, other_transducer);
            }
        };
    
        transducers.Reducible.prototype.map = function(f) {
            new_transducer = this.comp( transducers.map(f) );
            return new transducers.Reducible(this.coll, this.reduce, new_transducer);
        };
    
        transducers.Reducible.prototype.filter = function(pred) {
            new_transducer = this.comp( transducers.filter(pred) );
            return new transducers.Reducible(this.coll, this.reduce, new_transducer);
        };
    
        transducers.reduce = function(coll) {
            if(transducers.isString(coll)) {
               return new transducers.Reducible(coll, transducers.stringReduce, null);
            } else if(transducers.isArray(coll)) {
                return new transducers.Reducible(coll, transducers.arrayReduce, null);
            } else if(transducers.isIterable(coll)) {
                return new transducers.Reducible(coll, transducers.iterableReduce, null);
            } else if(transducers.isObject(coll)) {
                return new transducers.Reducible(coll, transducers.objectReduce, null);
            } else {
                throw new Error("Cannot reduce instance of " + coll.constructor.name);
            }
        };
       
        transducers.array = {}
        transducers.array.init = function() { return []; };
        transducers.array.result = function(result) { return result; };
        transducers.array.step = function(result, input) { result.push(input); return result; };
        
        transducers.sum = {}
        transducers.sum.init = function() { return 0; };
        transducers.sum.result = function(result) { return result; };
        transducers.sum.step = function(result, input) { return result + input; };

~~~
dwenzek
The code can be simplified, removing all stuff related to null. Initialise the
transducer field of reducibles with the identity transformation !

------
warble
From: [http://phuu.net/2014/08/31/csp-and-
transducers.html](http://phuu.net/2014/08/31/csp-and-transducers.html)

"To me, transducers are a generic and composable way to operate on a
collection of values, producing a new value or new collection of new values.
The word 'transducer' itself can be split into two parts that reflect this
definition: 'transform' — to produce some value from another — and 'reducer' —
to combine the values of a data structure to produce a new one."

~~~
qubyte
Amazingly, I was at the asyncjs.com presentation on these and related topics
by that author just yesterday! Excellent stuff.

------
jlongster
It'll be interesting to look into how the Clojure people implemented this. I
released the same library a few weeks ago:
[https://github.com/jlongster/transducers.js](https://github.com/jlongster/transducers.js),
and I'm about to release the next version with an API almost the same as the
OP. I think mine has more integration with JS data structures and stuff like
monkeypatching existing ones (like immutable-js) to work with this. We'll see
how all this shakes out though; not sure if it'll be good to converge on one
official library or if it's ok to have multiple.

~~~
sriku
The concept is simple enough and the performance benefits common enough that
it would certainly help to converge and contribute the core to one repo with
compatibility and other features in supplementary optional repos ... imho.

------
aikah
So I checked the definition of a transducer,but i'm not smart enough to get
it. What the difference between that library and say lodash where I can bind
and compose functions? because it seems like the same stuff

    
    
        var f=_.compose( _.partialRight(_.filter,isEven),_.partialRight(_.map,inc)) ;
    

or something.

~~~
icholy
I think it only iterates over the collection once.

~~~
msoad
Then what happens to operations that access the collection? like third
argument in JavaScript's native forEach?

~~~
acjohnson55
That's what a zipper [1] is good for. It gives you access to the rest of a
collection relative to the current position.

[1]
[http://en.wikipedia.org/wiki/Zipper_%28data_structure%29](http://en.wikipedia.org/wiki/Zipper_%28data_structure%29)

------
zoomerang
As somebody with more experience with Haskell and Scala than Clojure - how to
Transducers differ from Functors in Haskell?

On the surface they seem very similar.

------
tripzilch
Friendly advice: maybe also include the output of the example code in the
README.

------
coding4all
Notes and examples of Transducers and Reducers
[https://gist.github.com/runexec/06b56a9dbd15e43145b9](https://gist.github.com/runexec/06b56a9dbd15e43145b9)

------
d--b
Why not wait for ES6, and use proper iterators and use them as they do in C#?

as in var evens = [1,2,3,4].where(x => x%2 == 0).toarray()

no ?

