SlideShare a Scribd company logo
Transducers
What are they?
How can you use them in JavaScript?
Pavel Forkert
Programming languages &
design patterns geek
Basics
map
[1, 2, 3, 4, 5].map(function(element) {
return element * 2;
});
=> [2, 4, 6, 8, 10]
filter
[1, 2, 3, 4, 5].filter(function(element) {
return element % 2 === 0;
});
=> [2, 4]
reduce
[1, 2, 3, 4, 5].reduce(function(accumulator, element) {
console.log(accumulator, element);
return accumulator + element;
}, 0);
0 1
1 2
3 3
6 4
10 5
=> 15
Transducers in JavaScript
Transducers in JavaScript
Reducers
[0, 1, 2, 3, 4, 5, 6, 7]
.map(function(element) { return 1 << element; })
.reduce(function(acc, element) {
return acc + element;
}, 0);
=> 255
Reducers
var powerOfTwo = function(element) { return 1 << element; };
var map = function(fn, coll) { return coll.map(fn); };
var reduce = function(step, initial, coll) {
return coll.reduce(step, initial);
};
reduce(
function(acc, element) { return acc + element; },
0,
map(powerOfTwo, [0, 1, 2, 3, 4, 5, 6, 7]));
Reducers
var map = function(fn, coll) {
return { fn: fn, coll: coll };
};
var reduce = function(step, initial, transformResult) {
var coll = transformResult.coll;
var tfn = transformResult.fn;
var newStep = function(acc, element) {
return step(acc, tfn(element));
};
return coll.reduce(newStep, initial);
};
Reducers
var map = function(fn, coll) {
return {
reduce: function(step, initial) {
return coll.reduce(function(acc, element) {
return step(acc, fn(element));
}, initial);
}
};
};
Reducers
var reduce = function(initial, step, coll) {
return coll.reduce(step, initial);
};
Reducers
var filter = function(pred, coll) {
return {
reduce: function(step, initial) {
return coll.reduce(function(acc, element) {
return pred(element) ? step(acc, element) : acc;
}, initial);
}
};
};
Reducers
var even = function(element) { return element % 2 === 0; };
reduce(
function(acc, element) { return acc + element; },
0,
filter(even, map(powerOfTwo, [0, 1, 2, 3, 4, 5, 6, 7])));
=> 254
reduce(
function(acc, element) { return acc + element; },
0,
map(powerOfTwo, filter(even, [0, 1, 2, 3, 4, 5, 6, 7])));
=> 85
Transducers in JavaScript
–Rich Hickey
“Using these directly is somewhat odd”
Transducers in JavaScript
Why?
Keep advantages of reducers
Decouple transformations from inputs and outputs
Allow transformations to work in different contexts (channels, eager
collections, lazy collections, queues, event streams)
Composability!
transducers
transforming reducers
Transducers
(accumulator, element) -> accumulator
((accumulator, element) -> accumulator) ->

((accumulator, element) -> accumulator)
function map(fn) {
return function(step) {
return function(accumulator, element) {
return step(accumulator, fn(element));
};
};
};
Reducers
var map = function(fn, coll) {
return {
reduce: function(step, initial) {
return coll.reduce(function(acc, element) {
return step(acc, fn(element));
}, initial);
}
};
};
Transducers
var map = function(fn, coll) {
return {
reduce: function(step, initial) {
return coll.reduce(function(acc, element) {
return step(acc, fn(element));
}, initial);
}
};
};
Reducers
reduce(
function(acc, element) { return acc + element; },
0,
map(powerOfTwo, [0, 1, 2, 3, 4, 5, 6, 7]));
Transducers
transduce(
map(powerOfTwo),
function(acc, element) { return acc + element; },
0,
[0, 1, 2, 3, 4, 5, 6, 7]);
https://github.com/cognitect-labs/transducers-js
Transducers
reduce(
map(powerOfTwo)(function(acc, element) { return acc +
element; }),
0,
[0, 1, 2, 3, 4, 5, 6, 7]);
Reduce as collection builder
var append = function(acc, element) {
acc.push(element);
return acc;
};
reduce(
append,
[],
[0, 1, 2, 3, 4, 5, 6, 7]);

=> [0, 1, 2, 3, 4, 5, 6, 7]
Transducers
transduce(
map(powerOfTwo),
append,
[],
[0, 1, 2, 3, 4, 5, 6, 7]);
=> [1, 2, 4, 8, 16, 32, 64, 128]
Transducers
var map = function(fn, coll) {
return transduce(
transducers.map(fn),
append,
[],
coll);
}
Transducers / map-into
var array = [];
array = transduce(map(powerOfTwo), append, array,
[0, 1, 2, 3]);
=> [ 1, 2, 4, 8 ]
array = transduce(map(powerOfTwo), append, array,
[4, 5, 6, 7]);
=> [1, 2, 4, 8, 16, 32, 64, 128]
Transducers
var put = function(object, pair) {
object[pair[1]] = pair[0];
return object;
};
transduce(
map(function(pair) { return [pair[1], pair[0]]; }),
put,
{},
{ hello: 'world', transduce: 'reduce', ecmascript: 6 });
=> { 6: 'ecmascript', world: 'hello', reduce: 'transduce' }
Transducers / into
into(
[],
map(powerOfTwo),
[0, 1, 2, 3, 4, 5, 6, 7]);
=> [1, 2, 4, 8, 16, 32, 64, 128]
into(
{},
map(swapKeysAndValues),
{ hello: 'world', transduce: 'reduce', ecmascript: 6 });
=> { 6: 'ecmascript', world: 'hello', reduce: 'transduce' }
Transducers
map(fn)
cat()
mapcat(fn)
filter(pred)
remove(pred)
take(number)
take-while(pred)
drop(number)
drop-while(pred)
take-nth(number)
distinct()
replace(object)
interpose(separator)
partition-by(fn)
partition-all(number)
map-indexed(fn)
keep(fn)
keep-indexed(fn)
dedupe()
Example
Select filenames that are not directories and which size is less than 5kb.
Extract lines that are longer than 80 chars from selected files.
Show distributions of line sizes.
Example
var fs = require(‘bluebird')
.promisifyAll(require('fs'));
var t = require('transducers-js');
var map = t.map,
filter = t.filter,
mapcat = t.mapcat;
var ta = require('transduce-async');
https://github.com/transduce/transduce-async
Select filenames that are not directories
and which size is less than 5kb
var filterFilenames = ta.compose(
map(function(fileName) {
return fs.statAsync(fileName).then(function(stat) {
return { size: stat.size, isDirectory: stat.isDirectory(),
fileName: fileName };
});
}),
filter(function(file) {
return !file.isDirectory && file.size < 5000;
}),
map(function(file) {
return file.fileName;
})
);
Extract lines that are longer than 80
chars from selected files
var extractLines = ta.compose(
map(function(fileName) {
return fs.readFileAsync(fileName, 'utf8');
}),
mapcat(function(contents) {
return contents.split("n");
}),
filter(function(line) {
return line.length > 80;
})
);
Show distributions of line sizes
var sizes = ta.compose(
filterFilenames,
extractLines,
map(function(line) { return line.length; }));
var putCounts = function(o, e) {
o[e] = o[e] || 0;
o[e]++;
return o;
}
ta.transduce(sizes, putCounts, {}, fs.readdirSync('.'))
.then(function(count) {
console.log(count);
});
Transducers
Just functions… and objects
The essence of map/filter/etc (expressed through step-functions)
Completely decoupled from inputs and outputs
Composable way to build algorithmic transformations
Transducers in JavaScript
Go-like channels
var ch = chan();
go(function*() {
var val;
while((val = yield take(ch)) !== csp.CLOSED) {
console.log(val);
}
});
go(function*() {
yield put(ch, 1);
yield take(timeout(1000));
yield put(ch, 2);
ch.close();
});
… with transducers
var xform = comp(
map(function(x) {
return x * 2;
}),
filter(function(x) {
return x > 5;
}));
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));
}
});
=> 6, 8
Issues
No support for async stuff by default. transduce-async does workarounds,
but something like `filter` cannot be converted to async from the outside. IE:
filter-async is needed.
No groupBy, sort, etc.
Thanks
@fxposter
github.com/fxposter
blog.fxposter.org
fxposter@gmail.com
https://www.youtube.com/watch?v=6mTbuzafcII
https://www.youtube.com/watch?v=4KqUvG8HPYo
http://clojure.com/blog/2012/05/08/reducers-a-library-and-model-for-
collection-processing.html
http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
http://blog.cognitect.com/blog/2014/8/6/transducers-are-coming
http://jlongster.com/Transducers.js--A-JavaScript-Library-for-
Transformation-of-Data
http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-
JavaScript

More Related Content

Transducers in JavaScript