function compose(/*fns*/){
var fns = arguments;
return function(xf){
var i = fns.length - 1;
for(; i >= 0; i--){
xf = fns[i](xf);
}
return xf;
};
}This is abbreviated source for Transducers Explained Pipelines
composes an arbitrary number of functions
function compose(/*fns*/){
var fns = arguments;
return function(xf){
var i = fns.length - 1;
for(; i >= 0; i--){
xf = fns[i](xf);
}
return xf;
};
}calling manually
var value = plus1(plus1(plus2(5)));9
using composed function (allows reuse)
var plus4 = compose(plus1, plus1, plus2);
var value = plus4(5);9
Create a plus5 tranducer by composing map transducers.
var transducer = compose(
map(plus1), // [3,4,5]
map(plus2), // [5,6,7]
map(plus1), // [6,7,8]
map(plus1)); // [7,8,9]
var stepper = append;
var init = [];
var input = [2,3,4];
var output = transduce(transducer, stepper, init, input);[7,8,9]
The order of transformation is actually left to right.
function filter(predicate){
return function(xf){
return {
init: function(){
return xf.init();
},
step: function(value, item){
var allow = predicate(item);
if(allow){
value = xf.step(value, item);
}
return value;
},
result: function(value){
return xf.result(value);
}
};
};
}transducer that filters all odd values.
function isOdd(num){
return num % 2 === 1;
}
var transducer = filter(isOdd);
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[1,3,5]
another predicate
function isEqual(y){
return function(x){
return x === y;
};
}
var transducer = filter(isEqual(2));
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[2]
function not(predicate){
return function(x){
return !predicate(x);
};
}
var transducer = filter(not(isEqual(2)));
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[1,3,4,5]
increment each item and then filter odd values.
var transducer = compose(
map(plus1), // [2,3,4,5,6]
filter(isOdd)); // [3,5]
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[3,5]
swap these transducers and see what happens.
var transducer = compose(
filter(isOdd), // [1,3,5]
map(plus1)); // [2,4,6]
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[2,4,6]
This demonstrates two important properties of composed transducers:
function remove(predicate){
return filter(not(predicate));
}
var transducer = compose(
filter(isOdd), // [1,3,5]
map(plus1), // [2,4,6]
remove(isEqual(4))); // [2,6]
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[2,6]
function drop(n){
return function(xf){
var left = n;
return {
init: function(){
return xf.init();
},
step: function(value, item){
if(left > 0){
left--;
} else {
value = xf.step(value, item);
}
return value;
},
result: function(value){
return xf.result(value);
}
};
};
}
var transducer = drop(2);
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[3,4,5]
It is our first example of a transducer that creates a stateful transformation. State is created with the transformer, not the transducer.
function take(n){
return function(xf){
var left = n;
return {
init: function(){
return xf.init();
},
step: function(value, item){
value = xf.step(value, item);
if(--left <= 0){how do we stop???
}
return value;
},
result: function(value){
return xf.result(value);
}
};
};
}So how do we signal early termination? We’re going to have to take another look at our source of iteration: transduce.
function transduce(transducer, stepper, init, input){
if(typeof stepper === 'function'){
stepper = wrap(stepper);
}
var xf = transducer(stepper);
return reduce(xf, init, input);
}
function reduce(xf, init, input){
if(typeof xf === 'function'){
xf = wrap(xf);
}how do we stop??
var value = input.reduce(xf.step, init);
return xf.result(value);
}
function wrap(stepper){
return {
init: function(){
throw new Error('init not supported');
},
step: stepper,
result: function(value){
return value;
}
};
}
function reduce(xf, init, input){
if(typeof xf === 'function'){
xf = wrap(xf);
}
return arrayReduce(xf, init, input);
}
function arrayReduce(xf, init, array){
var value = init;
var idx = 0;
var length = array.length;
for(; idx < length; idx++){
value = xf.step(value, array[idx]);We need to break here, but how do we know?
}
return xf.result(value);
}We can wrap our reduced value in an object with two attributes:
value with the actual wrapped value__transducers_reduced__ of true signals the object is reduced.
function reduced(value){
return {
value: value,
__transducers_reduced__: true
};
}
function isReduced(value){
return value && value.__transducers_reduced__;
}
function deref(reducedValue){
return reducedValue.value;
}We can now adjust arrayReduce to handle early termination of reduced values.
function arrayReduce(xf, init, array){
var value = init;
var idx = 0;
var length = array.length;
for(; idx < length; idx++){
value = xf.step(value, array[idx]);
if(isReduced(value)){
value = deref(value);
break;
}
}
return xf.result(value);
}Now we are ready to complete our implementation of take:
function take(n){
return function(xf){
var left = n;
return {
init: function(){
return xf.init();
},
step: function(value, item){
value = xf.step(value, item);
if(--left <= 0){we are done, so signal reduced
value = reduced(value);
}
return value;
},
result: function(value){
return xf.result(value);
}
};
};
}
var transducer = take(3);
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[1,2,3]
var transducer = compose(
drop(1), // [2,3,4,5]
take(3), // [2,3,4]
drop(1)); // [3,4]
var stepper = append;
var init = [];
var input = [1,2,3,4,5];
var output = transduce(transducer, stepper, init, input);[3,4]