The Iterable concept is a way of providing a minimalist functional interface to a collection. In the way that the STL collections start from the
Container concept, the Iterable concept
provides a foundation for the OOTL collections.
A model of the Iterable concept applies a Procedure to values it contains in a sequential manner.
A simple example of an Iterable model in action is below:
void array_adaptor_test() {
writeln("This is a test of the array adapter");
writeln("Expect the numbers 1 to 3 in sequence separated by spaces");
int a[] = { 1, 2, 3 };
output(array_to_iterable(a, 3));
writeln();
}
The Iterable concept is more flexible than a Container since it can easily model
purely functional notions such as infinite series, or input streams, but it lacks the computational
power which comes with positional information. This is similar to the trade off inherent in the
usage of Input Iterators versus Random Access Iterators.
A model of the Iterable concept is expected to have shallow assignment semantics. Whether or not
an iterable is single pass, is not specified at this time.
Demonstration of the Iterable Concept
void array_adaptor_test() {
writeln("This is a test of the array adapter");
writeln("Expect the numbers 1 to 3 in sequence separated by spaces");
int a[] = { 1, 2, 3 };
output(array_to_iterable(a, 3));
writeln();
}
void generator_test() {
writeln("This is a test of the iterable generator.");
writeln("Expect 5 even numbers starting from 2.");
output(iterable_gen(2, next_even(), 5));
writeln();
}
void filter_test() {
writeln("This is a test of the filter and adapter.");
writeln("Expect the primes between 10-20");
output(filter(int_range(10, 20), is_prime()));
writeln();
}
template<typename Iterable, typename F, typename G>
void output_composition(Iterable i, F f, G g) {
output(compose(i, compose_fxn(f, g)));
}
void composition_test() {
writeln("This is a test of double composition.");
writeln("Expect the squares of the values from 1 to 10, minus 1");
output_composition(int_range(1, 11), square(), dec());
writeln();
}
void alternating_test() {
writeln("This is a test of conditional function composition.");
writeln("Expect numbers from 1 to 10 with odd numbers as negatives.");
output(
compose(
int_range(1, 11),
cond_fxn(
is_even(),
identity(),
negative()
)
)
);
writeln();
}
I actually implemented a major library which uses this concept heavily, to support both I/O and collections abstractions. The library is called Kataba Dynamics and you can find it at www.kataba.com.
> I actually implemented a major library which uses this > concept heavily, to support both I/O and collections > abstractions. The library is called Kataba Dynamics and > you can find it at http://www.kataba.com.
That is very interesting! Having used this design approach in an industratial setting do you have any anecdottes to share with me? For instance: In general does it work well? Are there scenarios where you found it especially useful, or cumbersome? Did you find using Java in this manner at all constraining?
The Iterable approach worked out really well. It makes a slew of things usable in exactly the same way: Iterators, StringTokenizers, Collections, byte-streams, char-streams, infinite series and computations, event sources, time ticks, etc. Wonderful.
The specific approach I used was to separate an Iterable (something you can iterate over) from at Iter (the actual iteration). Iter itself implemented Iterable, returning 'this' on a call to Iter.iter(). All the composition and processing methods took Iterable's as arguments, not Iter's, so could be applied to collections, iterations, or anything that could generate a stream of values.
I chose external iteration (hasNext/next) over internal iteration (for_each), partly to avoid having to implement a 'break;' concept in functions.
This worked out pretty well. Outerable/Outer took it to the next level, though. I think you can guess what they are: like Iterable/Iter, except you send values to them. It made a bunch of interop problems just go away. All the collections classes of Kataba Dynamics also implements Outerable. So, you could end up doing things like:
// composition_test Func_i sqr = new Func_i() { int f(int a) { return a*a; }}; ln(toString(map(map(count(1,10), sqr), dec_i)));
// alternating_test - no precise equivalent Func_i neg_if_odd = new Func_i() { int f(int a) { return a%2==0 ? a : -a; } }; ln(toString(map(count(1,11), neg_if_odd)));
To pull everything together, I wrote the collections I mentioned (com.kataba.coll), and a function library (com.kataba.func) which used behind-the-scenes bytecode compilation to make things go fast. I also implemented a macro language which lets you embed code-that-writes-code into your Java source.
Anyway, it turns out that selling a platform-like library is a really hard thing to do. Who would have guessed? :) So, I'm going to be open-sourcing it when I can find the time.
On another note: I like your use of _-separated names instead of camel-cased names. I've been leaning in the same direction recently, and it seems to be usefully easier to visually distinguish than the camel-casing. I think I picked it up from some Ruby source I was reading.