GeekLondon.com Help icon Syndication Feed icon 

Continuations in Java

Quite a while back, Sam Ruby posted a nice clear explanation of what Continuations are on his blog, entitled Continuations for Curmudgeons. I read this, and was enlightened.

That's easier said than done, as I take quite a bit of enlightening, but having got the hang of what the feature was all about, I started to wonder if it was possible to write code that's half as elegant in Java. Just as a reminder, and so you don't have to keep flipping back to Sam's article, here's how he implements a fibonacci series using continuations in Python:

def fib():
     yield 0
     i,j = 0,1
     while True:
       yield j
       i,j = j,i+j

Obviously the Java syntax is never going to be exactly the same. I was looking for a way to get the same behaviour with Java's slightly more verbose style. In the end I actually got quite close, but with some significant caveats. Here's my implementation of the Fibonacci logic using my pet Continuations for Java feature. It's not too far off.

public class Fibonacci extends Continuation<Integer> {
   public void run() throws Exception {
      yield(0);
      int i = 0;
      int j = 1;
      while (true) {
         yield(j);
         int current = i+j;
         i = j;
         j = current;
      }
   }
}

And here's the JUnit test case that proves that it works.

package com.fatmoggy.example;
 
import junit.framework.TestCase;
 
public class FibonacciTest extends TestCase {
   public void testFibonacci() {
      int[] fib = new int[] { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
            233, 377, 610, 987 };

      int index = 0;
      for (int x : new Fibonacci()) {
         if (x > 1000) break;
         assertEquals(x,fib[index++]);
      }
   }
}

So, given that I could write code like that, should you use an implementation like mine? Dear God No! the base class that's used by the above is an abomination. But still. Having written it, I'm kind of keen to show it off, but I disavow all responsibility for this code. You wouldn't catch me using it in real life.

Note that this is not just a question of someone cleaning up the various unfinished bits and rough edges (like the exception handling bug) - this is conceptually flawed; it'll never fly.

package com.fatmoggy.continuations;

 
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.SynchronousQueue;
 
/**
 * Represents a Continuation. Derived classes should over-ride the run method
 * and NOT return from it. Results are provided to the invoking class by
 * invoking the yield method with the appropriate result entry.
 *
 * The implementation works by using a Thread object to represent the context of
 * the continuation. The thread is suspended upon yield, and resumed when the
 * continuation is re-invoked. By implementing Iterable the continuation can be
 * used in an extended for loop.
 *
 * Don't use this code for anything. It's got lots of problems, not least the
 * overhead of the thread (and its stack) associated with every Continuation object!
 *
 * @author Dave Minter
 */
abstract public class Continuation<T> implements Iterable<T> {

   // Reference used to disambiguate the Runnable and Continuation run methods
   private Continuation<T> outer = this;

   // The thread which will be used to retained to represent 
   // this Continuation's context
   private final Thread thread = new Thread(
      // An implementation of Runnable which allows the continuation's
      // incompatible run() signature to be invoked
      new Runnable() {
         public void run() {
            try {
               outer.run();
            } catch (Throwable t) {
               throw new RuntimeException("Continuation Exception Wrapper", t);
            }
         }
      }, "Continuation: " + this.getClass().getName());

   // Obtain a thrown exception
   private Throwable throwable = null;
 
   // Used to synchronize consumption of the output of the continuation
   // with production of values
   private final SynchronousQueue<T> results = new SynchronousQueue<T>();
 
   /**
    * Provided to allow iteration over the continuation
    */
   final public Iterator<T> iterator() {
      final Continuation<T> me = this;
      return new Iterator<T>() {

         public void remove() {
            throw new UnsupportedOperationException();
         }
 

         public boolean hasNext() {
            return true;
         }
 
         public T next() {
            try {
               return me.next();
            } catch (Throwable e) {
               throw new NoSuchElementException(
                     "Continuation interrupted: " + e);
            }
         }
      };
   }
 
   /**
    * Provided for invocation by the extending class in place of
    * the normal return statement of a conventional method.
    * @param item The item to yield up to the consumer
    * @throws InterruptedException
    */
   final protected void yield(T item) throws InterruptedException {
      results.put(item);
   }
 
   /**
    * Allows direct invocation of the Continuation
    * @return The next value
    * @throws Throwable
    */
   final public T next() throws Throwable {

      // Ensure the thread maintaining the continuation is established
      initThread();

      // Grab a result from the queue (wakes up the producer)
      T item = results.take();
 
      // If the thread has expired, abend!
      if (throwable != null) throw throwable;

      // Return the item
      return item;
   }
 
   // Initializes the thread representing the continuation's context.
   private void initThread() {

      if (!thread.isAlive()) {
         // Continuation mustn't prevent app completion
         thread.setDaemon(true);
         // Death of the continuation needs to be handled.
         thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

            public void uncaughtException(Thread t, Throwable e) {
               // Unwrap the exception
               // TODO: Not working correctly; original exception is being lost
               throwable = e.getCause();
            }

         });
         // Start the continuation thread
         thread.start();
      }
   }
 
   /**
    * The producer method. Instead of returning, this method should make calls to yield()
    * @throws Throwable
    */
   abstract public void run() throws Throwable;
}

Still, who knows. Maybe we'll get Continuations in Dolphin (Java 7). And in the mean time we can revel in the knowledge that Java can do everything Python can do even if you'll run out of virtual memory in the process...

Posted at Feb 22, 2007 6:41:28 PM, and last updated Feb 22, 2007 6:41:39 PM