After reading Lifting in Flapjax, I did a little mind bending and came up with my version of lift_e, supporting arity larger than one and function-valued stream as the first parameter.
So basically the enhancements are:
- Since f may accept more than one arguments, we can support it by
- lift_e returning stream of curried f, which can be fed to lift_e again repeatedly
- lift_e accepting multiple streams as arguments of f: e1, e2 …
I have picked the former just because it was the first one came to my mind, and it sounds more reasonable. The latter implies a lot (probably meaningless) updates in the lifted stream.
- When f is a function-valued stream, each time it updates, the lifted is also renewed.
/* f is a function or a function-valued event stream.
e is an event stream.
Returns a "lifted" function-valued event stream which consists of curried
functions from f with values in e as the first argument.
A new value in e (or f if it's a stream) triggers the new value in "lifted".
Special work-arounds for empty values:
1. If f is a function-valued event stream, every value in e before the
appearance of the first value in f is ignored (since there is no function
to "lift" them). In the future if f provides an initial value this can be
patched.
2. In the same vein, if f is a stream but its first value comes before the
first value of e, no new value appears in lifted. Again, this can be patched
of e provides an initial value.
*/
function lift_e(f, e) {
/* Accepts a function of arbitrary arity and its first argument.
Returns a curried version taking one less arguments. */
function curry1(func, v) {
return function () {
var args = new Array();
for(var i=0; i < arguments.length; i++) {
args[i] = arguments[i];
}
// insert the new value in e at the beginning
args.unshift(v);
return func.apply(this, args);
};
}
/* Uses func to "lift" e and stores the latest value in e to lifted.curValue */
function liftAndStore(func) {
e.listeners.push(
function (v) {
lifted.newValue(curry1(func, v));
lifted.curValue = v;
}
);
}
var lifted = new Event();
// TODO initial value of e should be put here
lifted.curValue = undefined;
if (f instanceof Event) {
// f is a function-valued event stream
f.listeners.push(
function (fv) {
// fv is a function.
// A new function value in f triggers in lifted a new value
// which is a curried function taking one less argument
if (lifted.curValue !== undefined)
lifted.newValue(curry1(fv, lifted.curValue));
// TODO need a better way to remove exactly
// the previous function in f from e.listeners
e.listeners.pop();
liftAndStore(fv);
}
);
// TODO if f has an initial value, liftAndStore it here
} else {
// f is just a function
liftAndStore(f);
}
return lifted;
}
This has a flavor of mergeE since the resulting stream is triggered whenever f or e updates. Also, it can be seen as an operation satisfying the closure property (closure as in abstract algebra, not in programming langauges), or an OO decorator pattern.
In practice we can of course use the lifted stream in many different ways. Here for simplicity we provide arguments of curried f in a modified version of showStream called showStreamApplied, which delegates the presentation part to the former:
function showStreamApplied(e, args) {
var appliedStream = new Event();
e.listeners.push(
function(fv) {
appliedStream.newValue(fv.apply(this, args));
}
);
showStream(appliedStream);
}
To show its usage I have made 3 showcases. Feel free to comment out some of them to observe individual output.
function loader() {
// 1. curried functions in lifted do not need extra arguments
showStreamApplied(lift_e(function (v) { return 10000 * v; }, timer_e(500)));
// curried functions in func_e take (4-1)=3 extra arguemnts
var func_e = lift_e(function(a, t, prefix, suffix) {
return prefix + t + suffix;
},
// every sec
timer_e(1000));
// 2. feeding 3 arguments directly
showStreamApplied(func_e, ['unknown', 'system time ', '.']);
// 3. providing one argument by lifting again
showStreamApplied(lift_e(func_e,
// every 5 sec
timer_e(5000)),
['system time is ', '.']);
}
Note how first-class streams/behaviors objects in Flapjax hide callbacks away and make client code more lucid.
At last, you can see it run here.