Ok, let's continue with iterators. In part 1 I showed an approximation of streams which can be traversed step by step. You may wonder why bother? The answer is simple. I want to show how to build a library of iterative combinators, which can be easily composed to form larger iterative combinators, which can then be executed over an arbitrary collection. The collection, or the stream, together with the logic to enumerate it form an iterator.
What are these iterative combinators I am talking about?
Let's take foreach - a php language construct familiar to every php programmer and turn it into a function called map.
function map($f, $coll, $result) {
foreach( $coll as $val) {
$result.append( $fun($val) );
}
return $result;
}
The function takes a function $f and a collection $coll(an iterator) and returns a $result, which is some kind of a collection to which we can append the result of $f applied to each value of the collection. We can say that the function $f is mapped over the collection.
What will happen if we want to apply another function, let's say $g to the result of map($f, $coll, $r), presuming that $r is another iterator.
map($g, map($f, $coll, $r), $r2);
That is quite wasteful, since we will perform two traversals. This is where having step based functions helps.
function mapStep($f, $val) {
return $fun($val);
}
And the traversal of the collection is outsourced to something on the outside. Concepts very similar to these are widely used within the context of functional programming.
Let's write down some of the typical functions:
//a step version of reduce (fold)
function reduceStep( $fun, $coll, $result ) {
return $fun( $coll, $result );
}
//a step version of expand (unfold - the dual to fold)
function expandStep($fun, $pred, $seed ) {
return $pred( $coll ) ? $fun( $seed ): Null;
}
//a step version of filter
function filterStep( $pred, $val) {
return $pred( $coll.current() )? $val: null;
}
//a step version of zip
function zipStep( $val1, $val2 ) {
return array( $val1, $val2 );
}
//list() is the step version of unzipStep (dual to the above)
With these functions we can chain the processing of a collection while avoiding multiple iterations. Using the plain functions very quickly becomes unwieldly - too much syntax noise. Add to it the need to handle nulls and other exceptional values and it quickly becomes messy. A class similar to the step class in part 1 helps by introducing method chaining (or fluent interfaces). Such converting of a sequence of collection traversals and transformations is sometimes called fusion. It usually results in reduction of memory and allocations used by a program and is a known compiler optimisation. PHP is not that kind of language, and we can't expect the compiler to do it for us, but we can do it ourselves, whithout sacrificing too much.
In later posts, I will show some examples and will continue towards a link with observers, aspects and other esoteric techniques, which can form a small but powerful toolkit for writing short and flexible code.