Continuations in Java


Continuations sind eine nützliche Sache, ob nun beim Compilerbau oder für Webanwendungen. Kein Wunder, dass Scala Continuations (genauer gesagt „Delimited Continuations“) schon „eingebaut“ hat, und dass auch für Java entsprechende Bibliotheken wie javaflow oder RIFE existieren (hier ein kleines Beispiel für die Anwendung). Übrigens bin ich alles andere als ein Experte auf diesem Gebiet, ich verstehe so ungefähr die Idee dahinter, aber praktische Erfahrungen damit habe ich nicht.

Allerdings hat mich diese Haskell-Wiki-Seite neugierig gemacht, wo die Arbeitsweise der Cont-Monade in Haskell erklärt wird. Der Code auf dieser Seite lässt sich fast eins zu eins nach Java übertragen, wenn man von fehlenden Closures (a.k.a. Lambdas) einmal absieht, und irrwitzige Typ-Signaturen in Kauf nimmt. Zuerst helfen wir Java mit ein paar Pseudo-Closures auf die Sprünge:

public interface F1<A,B> {
    public B $(A a);
}

//nur zur Bequemlichkeit
public abstract class F2<A,B,C> implements F1<A,F1<B,C>> {

    public abstract C $(A a, B b);

    @Override
    public F1<B,C> $(final A a) {
        return new F1<B,C>() {
            @Override public C $(B b) {
                return F2.this.$(a,b);
            }
        };
    }
}

Man verzeihe mir den etwas ungewöhnlichen (aber völlig „legalen“) Methodennamen $, bei dem es sich um einen kleinen Pun auf Haskells $ handelt, das auch nichts anderes tut, als eine Funktion mit ihrem Argument aufzurufen. Nun die Cont-Monade in Minimalausstattung, nämlich der return-Funktion (die mit Javas return überhaupt nichts zu tun hat) und bind (in Haskell wäre das der (>>=)-Operator):

public abstract class Cont<R,A> {

    public abstract R runCont(F1<A,R> fn);

    public static <R,T> Cont<R,T> Return(final T t) {
        return new Cont<R,T>() {
            @Override public R runCont(F1<T,R> fn) {
                return fn.$(t);
            }
        };
    }

    public <B> Cont<R,B> bind(final F1<A,Cont<R,B>> f){
        return new Cont<R,B>() {
            @Override public R runCont(final F1<B, R> k) {
                return Cont.this.runCont(new F1<A,R>(){
                    @Override public R $(A a) {
                        return f.$(a).runCont(k);
                    }
                });
            }
        };
    }
}

Diese Definition ist direkt aus dem Haskell-Wiki abgeschrieben, wobei ich natürlich Cont und seine Monaden-Typklassen-Instanz zusammengefasst habe (sonst wäre die Verwendung noch umständlicher geworden). Ich habe einmal das dortige Pythagoras-Beispiel implementiert, um zu zeigen, dass der Code wirklich funktioniert.

public class PythExample {

    public static <R> Cont<R, Integer> addCont(Integer a, Integer b) {
        return Cont.Return(a + b);
    }

    public static <R> Cont<R, Integer> squareCont(Integer x) {
        return Cont.Return(x * x);
    }

    public static <R> F2<Integer, Integer, Cont<R, Integer>> pythCont() {
        return new F2<Integer, Integer, Cont<R, Integer>>() {
            @Override public Cont<R, Integer> $(final Integer x, final Integer y) {
                return PythExample.<R>squareCont(x).bind(new F1<Integer, Cont<R, Integer>>() {
                    @Override public Cont<R, Integer> $(final Integer a) {
                        return PythExample.<R>squareCont(y).bind(new F1<Integer,Cont<R,Integer>>() {
                            @Override public Cont<R, Integer> $(final Integer b) {
                                return PythExample.<R>addCont(a, b);
                            }
                        });
                    }
                });
            }
        };
    }

    public static <R> F1<R, Void> print() {
        return new F1<R, Void>() {
            @Override public Void $(R r) {
                System.out.println(r);
                return null;
            }
        };
    }

    public static void main(String... args) {
        PythExample.<Void>pythCont().$(3, 4).runCont(PythExample.<Integer>print());
    }
}

Wie erwartet gibt dieses Monster 25 aus. Ich weiß, das Beispiel ist nicht besonders cool, aber es funktioniert, und viel mehr will ich mir bei der aktuellen Syntax auch nicht antun – an pythCont() habe ich eine ganze Weile gebastelt. Natürlich habe ich mir die Frage gestellt, wie das ganze mit besseren Lambdas laufen würde, und sowohl lambdaj– wie auch Approval Test-Closures getestet, beide ohne Erfolg: Erstere hatten keine geeignete Typsignatur, letztere führten den Code schon beim Erstellen (und dann natürlich nochmal beim eigentlichen Aufruf) aus, beides absolute Show-Stopper.

Es spricht nichts dagegen, den vorhandenen Code weiter aufzubohren (z.B. callCC, mehr Monaden-Funktionen, Simulation von do-Blöcken, sinnvollere Beispiele), auch wenn das schnell haarig werden kann. Das sieht natürlich anders aus, sobald Java „echte“ Closures unterstützt. Trotzdem ist es cool, dass sich Continuations ohne Bytecode-Manipulation, Reflection o.ä. in Java implementieren lassen.

Update:
Hier die gleiche Funktionalität in Java 8: Continuations in Java 8

Advertisements

Ein Gedanke zu “Continuations in Java

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s