C# Continuations useful? Hell yeah!

Last week, Sam Ruby posted a very interesting article on continuations for "people older than dirt" (a category which I, according to his definition, fall into). The topic became even more interesting when Don Box posted how you can use a very similar syntax in the next iteration of C#. Shortly thereafter, Cedric Beust posted that he wasn't convinced on the usefulness of this construct, and that a simple Java class without continuations pretty much does the same thing.

Despite having, like Cedric, mainly a Java background, I do think this construct will be useful, and would welcome it in the next release (that seems to be the established pattern anyway). Consider the example of trying to write a filtered Iterator. Using the C# 2.0, the code almost writes itself:

    class Program
    {
        public static IEnumerable<T> IteratorFilter<T>(IEnumerable<T> iterator,
                                                       Predicate<T> predicate)
        {
            foreach (T value in iterator)
            {
                if (predicate(value))
                {
                    yield return value;
                }
            }
        }

        static void Main()
        {
            int[] values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
            foreach(int i in IteratorFilter(values, 
                    delegate(int v){ return v%2 == 0; }))
            {
                Console.WriteLine(i); // even numbers only
            }
        }
    }
The same can definitely not be said for Java. My initial version took quite a bit longer to create than the C# version, and ended up looking like this:
    public interface Predicate<T> {
        boolean evaluate(T value);
    }

    public class IteratorFilter<T> implements Iterator<T> {
        private Iterator<T> _iterator;
        private Predicate<T> _predicate;
        private T _currentValue;
        private boolean _hasNext = true;
    
        public IteratorFilter(Iterator<T> iterator, Predicate<T> predicate) {
            _iterator = iterator;
            _predicate = predicate;
            skipFiltered();
        }
    
        private void skipFiltered() {
            while (_iterator.hasNext()) {
                _currentValue = _iterator.next();
                if (_predicate.evaluate(_currentValue)) {
                    return;
                }
            }
            _hasNext = false;
        }
    
        public boolean hasNext() {
            return _hasNext;
        }
    
        public T next() {
            if (!_hasNext) {
                throw new NoSuchElementException();
            }
            T result = _currentValue;
            skipFiltered();
            return result;
        }
    
        public void remove() {
            throw new UnsupportedOperationException();
        }
    
        public static void main(String[] args) {
            Integer[] values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
            Iterator<Integer> i = new IteratorFilter<Integer>(
                    Arrays.asList(values).iterator(), 
                    new Predicate<Integer>() {
                        public boolean evaluate(Integer value) {
                            return value % 2 == 0;
                        }
                    });
    
            while(i.hasNext()) {
                System.out.println(i.next());
            }
        }
    }
Ouch! Without continuations, there's no option to just iterate over the elements and filter out the unwanted values, like I did in the C# implementation. Instead, I'm forced to create a class that implements the Iterator interface, store _currentValue and _hasNext as fields, and create a skipFiltered() method to skip unwanted values.

I am aware that C#'s IEnumarable isn't exactly the same as a Java Iterator; in a way it's more like a subset of Java's Collection. However, creating a Java function that creates a new Collection (containing the filtered subset of the original) wouldn't be quite the same as it would create a second Collection and have all filtering done up-front, instead of just an iterator whose elements are fetched from the original in an on-demand fashion.

Disclaimer: I am by no means an expert on the new constructs in C# 2.0 or Java 5.0 (far from it) - if I've overlooked something, please let me know in the comment section below.

TrackBack URL for this entry: http://www.hutteman.com/scgi-bin/mt/mt-tb.cgi/187
Comments

See also Don Box's recent blog entries for the sort of functional programming you can do when using generics and continuations: http://www.pluralsite.com/blogs/dbox/default.aspx.

Posted by Richard at April 26, 2005 2:55 AM

I totally agree they are very very useful. One of the prime uses I've found is with an XmlReader. You can read through an XML document using XmlTextReader, yielding a new object everytime you hit something useful in the XML document. It's a great way to turn a firehose-style cursor with an unusual API into an OO iterator which can be consumed with foreach(). When we wrote NChant (nchant.sourceforge.net) we used the 'yield return' keyword extensively to filter sets into subsets.

Posted by RichB at April 26, 2005 4:48 AM

Contiuations look like to be useful. But Java was designed to be C++--: The C++ language without the garbage stuff and syntax. But C# looks like Java++--: The Java language with some new useful additions that turn it into a near-C++ dirty syntax and adds the old C++ garbage to it.

People say that Java went after C# and added the generics stuff to the language. The truth is that, this feature request was on the list at Bug Parade for some years with many votes but that was the garbage that Java was trying to remove from the language. Then, Microsoft added those old garbage (and sometimes useful) stuff to C# and used it as marketing campaign against Java. So Sun couldn't do anything but accept the expectations of its customers and add the generics stuff to the Java language.

Continuations are good. But when someone designs a programming language he has to consider tradeoffs: Say, do I want to design a language with as much syntactical sugar as Perl or do I want to keep the syntax clean and add powerful libraries to its core or both of them? Java was designed with the second principle in mind. C# is going to look like the third one... In marketing terms C# may win, but I prefer a language with a clean syntax... Now for marketing reasons, I guess Java's syntax will turn to something as ugly as C#...

Posted by Behrang Saeedzadeh at April 26, 2005 5:24 AM

A minor stylistic suggestion: you chosen to advance the input interator in your constructor. You might consider delaying that until the user actually starts using the iterator so that the price of advancing the input iterator is paid at a more appropriate time.


In the equivalent class in the jga library, I used a Boolean rather than boolean, and cleared the reference when the wrapper iterator had returned a reference (essentially using the null state of the reference to indicate 'I don't know if there's a next() or not').


If you haven't seen jga (http://jga.sf.net/), consider taking a look. It is a reasonably complete adaptation of STL to java idioms.

Posted by David Hall at April 26, 2005 9:35 AM

Sorry Luke, but what you are demonstrating in this article is a generator, not a continuation...

The challenge is still on :-)

--
Cedric

Posted by Cedric at April 26, 2005 10:02 AM

Well strictly speaking, .NET doesn't have continuations, just a continuation-like syntax for generators. Multiple calls to IteratorFilter() will return new IEnumerable objects, not continue from where the previous one left off.

You have to admit though that the "yield return" construct makes the C# code a lot cleaner than the Java version.

I'll leave the discussion on actual continuations to the Ruby experts.

Posted by Luke Hutteman at April 26, 2005 10:12 AM
values = range(10)
for nr in (nr for nr in values if nr%2==0): 
    print nr
Posted by gheorghe at April 26, 2005 3:16 PM

Yuck, this Python code is really ugly. Five "nr" variables?!?

Posted by Cedric at April 26, 2005 6:33 PM

Python keeps rubbing me the wrong way
In response to Luke Hutteman's post on continuations, someone offered the following Python snippet to illustrate filtering: values = range(10) for nr in (nr for nr in values if nr%2==0):...

Trackback from Otaku, Cedric's weblog at April 26, 2005 6:54 PM
Now for marketing reasons, I guess Java's syntax will turn to something as ugly as C#...

Actually, worse - since Sun's reluctance to introduce new keywords will probably result in more ugly constructs like "for (SomeClass x: SomeCollection) { }" (as opposed to the much cleaner C# foreach version).

Posted by Luke Hutteman at April 26, 2005 7:47 PM

If this was a key feature, why wasn't an extract of real code used rather cooking up an example?

Nice to see like has been compared with like. Using an anonymous delegate stuffed onto one line (that disappears behind the adverts) for the C# and an outer class with non-standard layout for the Java.

BTW: You probably want to implement Iterator<T> for it to be useful.

Posted by Tom Hawtin at April 27, 2005 4:27 PM

Using the jga library in Java the example is:

final List< Integer > list = asList( new Integer[] { 1, 2, 3 } ); // create list
final UnaryPredicate< Integer > even = new UnaryPredicate< Integer >() { // filter function
        public Boolean fn( final Integer x ) { return x%2 == 0; }
};
for ( final Integer i : filter( list, even ) )
        System.out.println( i ); // print filtered list

I think this code is simpler to understand; the C# code has a double loop, one for the generator and one for the reader. Also the code is likely to be easier to debug; the C# code is transformed into something like the Java code in the compiler, therefore there is no simple mapping between runtime and source.

The example shows how inner classes in Java give you continuations (in iterator sense), iterators, generators, closures, blocks, first class functions, functors etc. in one concept. They have a number of other advantages as well:

1. Easy to understand - they are just a class
2. Easy to refactor as a stand alone class
3. Can inherit from existing classes and interfaces - e.g. UnaryPredicate above
4. Can have fields
5. Can have multiple methods

Posted by Howard Lovatt at April 27, 2005 7:49 PM

Tom: I'd use real code if I had any, but I don't use .NET 2.0 in my day-job. The .NET code was written while trying to teach myself this new construct. The subsequent Java code was then written simply to try and get the same functionality using a language I do use on a daily basis (well, Java 1.4 anyway - unfortunately I don't use Java 5 in my day-job yet either).

I added some linebreaks that should help prevent the code from disappearing off the screen - running 1600x1200 myself it looked fine on my screen, but of course most people don't run on that resolution.

Regarding implementing Iterator<T> - you're absolutely right, thank you. Fixed.

Posted by Luke Hutteman at April 27, 2005 11:36 PM

Howard: I'm not familiar with jga, but it looks interesting.

As far as debugability goes, using VS.NET you debug the code as-is, so for every iteration in the foreach in Main(), the debugger will jump to the foreach in IteratorFilter to retrieve the next value. It's very intuitive (kind of like how current Java IDEs allow you to debug a JSP as-is, instead of forcing you to deal with the generated Servlet like they used to do).

The way I understand the jga code though, it's not filtering on the fly, but rather creating a new (filtered) Collection based on the original one; or does jga's filter() method return a virtual collection that still uses the original collection as a backing store?

Posted by Luke Hutteman at April 27, 2005 11:48 PM

While I'm glad to see that people here have already pointed out that generators are not continuations, and shouldn't be called such, no one has really pointed out what full continuations can do. In theory, the generator usage of continuations could be emulated with some compiler transformations and some anonymous classes. (Which is, in fact, what I think the C# compiler does internally)

However, that won't let you get to a classic continuation example: amb. Here's the standard scheme implementation, and here's a ruby version I made by ripping off the scheme implementation.

However, before you look behind the curtain to see the little man pulling the wires, maybe you should look at a short example of what this lets you do. Yes, that's right: backtracking ala prolog right in the middle of your ruby code. (the example was also ripped out of the scheme page referenced earlier)

yield is neat, but C# doesn't have actual continuations yet. (Whether the still missing continuation functionality is actually useful in practice is beyond the scope of this post)

Posted by Daniel Martin at April 28, 2005 7:27 AM

Generics was more easy.
Generics was more easy.

Trackback from 菊池 Blog at May 24, 2005 10:22 PM

Here is a proper example of generators using Python that is equivalent to the C# code:

def IteratorFilter(alist, predicate):
    for value in alist:
        if (predicate(value)):
            yield value
def main():
    values = range(10) # same as [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
    for i in IteratorFilter(values, lambda v: v%2 == 0):
        print i
if __name__ == "__main__":
    main()
Posted by Grant McDonald at June 15, 2005 8:42 PM

Hello Luke,
in fact the JGA filter() returns an Iterable, so it works pretty much like your original Java counter-example with Iterator, with the principal difference that it can be used in the "new for" construct.

I'm not sure it makes a difference eight months after your posting, but here it is...

Posted by Dimitrios Souflis at December 20, 2005 10:08 AM

A note for Behrang: it's not the syntax, it's the semantics. Ie., no language has simpler syntax than Scheme, which arguably has no syntax at all...but Scheme has full-fledged continuations, closures, and code generation built into the language. The powerful semantics can make your code a lot shorter.

Posted by dennis at January 4, 2006 12:46 PM
This discussion has been closed. If you wish to contact me about this post, you can do so by email.