mercredi 14 octobre 2009
Musing about generators, oni and functional programming
Par David Marteau, mercredi 14 octobre 2009 à 12:24 :: Programming
While playing with the great ONI javascript library from Alex Fritze (which is now used in our our new zoomcreator product) , I was striked how generators can provide very useful patterns in javascript.
Generators have been introduced in javascript 1.8 in the gecko JS engine and are a common feature in python using the same 'yield' operator.
The main motivation for such an operator is to offer an alternative to callback function when dealing with producer function. With the yield operator, one can create an iterator that iterates over the "production" step and delegates to other pattern the handle of the production chain management.
Saying that, it is very simple to create ONI patterns for managing the asynchronous part in one hand and the production mecanism in the other hand.
Example: let us say that we have an asynchronous oni expression that let us update a progress bar.
var DoProgress = oni.SLift( function ( aGen ) {
try {
let val = aGen.next(); // get the next value
// Update our progress bar
....
return false;
} catch(err if err instanceof StopIteration) {
return true;
}
} );
Create an asynchronous loop and run it.
oni.Loop(oni.If(DoProgress(gen), oni.Break())).run();
where "gen" is a generator provided by another part of the code.
In this example, using the generator enables us to write a generic pattern for dealing with asynchronous stuff without knowing absolutely anything from the underlying process.
Generators and functional programming:
Oni has a radically functional approach, and this was very appealIing to me. Following the same line of thoughts and motivated by some other existing examples of functional approach of javascript programming (as found in http://osteele.com/sources/javascript/functional/) I have been tempted to use generators for derivating some common patterns of functional programming.
First of all, generators define sequences and thus all common patterns using sequences can be defined on it (like iiterators in C++).
For example one can define the following classical functions:
function map( f, seq ) {
for(let x in seq) yield f(x);
}
function reduce( f, init, seq ) {
let u = init;
for(let x in seq) { u = f(u,x); yield u; }
}
function filter( f, seq ) {
for(let x in seq)
if(f(x)) yield x;
}
Or even other more 'Lisp' oriented functions:
function cons( obj, seq ) {
yield obj;
for(let x in seq) yield x;
}
Will generate a new sequence concatenating the first argument to the sequence passed as second parameter.
function car( seq ) {
try {
return seq.next();
} catch(err if err instanceof StopIteration) {}
return null;
}
Evaluate the first element of the sequence and return its value.
function cdr( seq ) {
try {
return (seq.next(),seq);
} catch(err if err instanceof StopIteration) {}
return null;
}
Evaluate the first element of the sequence by calling next() and return the sequence. This pattern use the fact that evaluating sequence elements actually remove the element from the sequence.
A more complete set of functions can be found here as a Javascript module.
Note that there are interesting differences between classical sequences (as Arrays) and generators:
- first, generators consume the sequence as it is iterated: when I step to the next item in sequence, my sequence does not hold that value any more (remember, it is an iterator)
- second, we have a form of lazy evaluation because we never construct an array of the computed values of the sequence, so we can have any arbitrary length (even infinite) in our sequences. This lazy evalutation is the most interesting part of using generators in such approach.
Thanks to Alex Fritze for motivating me to talk about this when we met at the MozCamp in Prague.