Transducers in Javascript

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

Transducers in Javascript.

This is an interesting sentence:

The reduce function is the base transformation; any other transformation can be expressed in terms of it (map, filter, etc).

All of the programming languages seem to be adding all of the same features. Over the last 2 years, suddenly people woke up and realized they could escape “callback hell” by using finite state machines to enable a pleasant API for async programming, core.async being a good Clojure example.

Now James Long adds transducers to Javascript:

Let’s play around with all the kinds of data structures we can use now. A type must at least be iterable to use with into or transduce, but if it is also buildable then it can also be used with sequence or the target collection of into.

var xform = compose(map(x => x * 2),
filter(x => x > 5));

// arrays (iterable & buildable)

sequence(xform, [1, 2, 3, 4]);
// -> [ 6, 8 ]

// objects (iterable & buildable)

into([],
compose(map(kv => kv[1]), xform),
{ x: 1, y: 2, z: 3, w: 4 })
// -> [ 6, 8 ]

sequence(map(kv => [kv[0], kv[1] + 1]),
{ x: 1, y: 2, z: 3, w: 4 })
// -> { x: 2, y: 3, z: 4, w: 5 }

// generators (iterable)

function *data() {
yield 1;
yield 2;
yield 3;
yield 4;
}

into([], xform, data())
// -> [ 6, 8 ]

// Sets and Maps (iterable)

into([], xform, new Set([1, 2, 3, 3]))
// -> [ 6 ]

into({}, map(kv => [kv[0], kv[1] * 2], new Map([[‘x’, 1], [‘y’, 2]])))
// -> { x: 2, y: 4 }

// or make it buildable

Map.prototype[‘@@append’] = Map.prototype.add;
Map.prototype[‘@@empty’] = function() { return new Map(); };
Set.prototype[‘@@append’] = Set.prototype.add;
Set.prototype[‘@@empty’] = function() { return new Set(); };

sequence(xform, new Set([1, 2, 3, 2]))
sequence(xform, new Map([[‘x’, 1], [‘y’, 2]]));

// node lists (iterable)

into([], map(x => x.className), document.querySelectorAll(‘div’));

// custom types (iterable & buildable)

into([], xform, Immutable.Vector(1, 2, 3, 4));
into(MyCustomType(), xform, Immutable.Vector(1, 2, 3, 4));

// if implemented append and empty:
sequence(xform, Immutable.Vector(1, 2, 3, 4));

// channels

var ch = chan(1, xform);

go(function*() {
yield put(ch, 1);
yield put(ch, 2);
yield put(ch, 3);
yield put(ch, 4);
});

go(function*() {
while(!ch.closed) {
console.log(yield take(ch));
}
});
// output: 6 8

Now that we’ve decoupled the data that comes in, how it’s transformed, and what comes out, we have an insane amount of power. And with a pretty simple API as well.

Did you notice that last example with channels? That’s right, a js-csp channel which I introduced in my last post now can take a transducer to apply over each item that passes through the channel. This easily lets us do Rx-style (reactive) code by simple reusing all the same transformations.

A channel is basically just a stream. You can reuse all of your familiar transformations on streams. That’s huge!

Post external references

  1. 1
    http://jlongster.com/Transducers.js--A-JavaScript-Library-for-Transformation-of-Data
  2. 2
    http://hueypetersen.com/posts/2013/08/02/the-state-machines-of-core-async/
Source