Applikative Funktoren in highJ


Heute will ich einmal ein kleines Beispiel bringen, wie man die highJ-Bibliothek verwenden kann. Das Beispiel in Haskell sieht harmlos genug aus:

import Control.Applicative

filter (and . ([ (>2) , (<7) , odd ] <*>) . pure) [1..10]
--[3,5]

Was passiert, ist ziemlich klar: Eine Liste von 1 bis 10 wird gefiltert, und zwar nach einer Reihe von Kritierien. Die filter-Funktion erwartet eigentlich nur ein einziges Kriterium, also stellt sich die Frage, wie der seltsame Ausdruck in der Mitte funktioniert.

Die pure-Funktion verpackt einen Wert in einen applikativen Funktor. Für eine Liste heißt das, das Argument wird zu einer ein-elementigen Liste. Der seltsame Operator <*> (auch ap genannt) führt in einen applikativen Funktor verpackte Funktionen auf in einen applikativen Funktor verpackte Argumente aus, z.B. ergibt [(>2), (<7), odd] <*> [7] den Wert [True,False,True]. Hätte man mehrere Argumente, würden alle möglichen Kombinationen aus einer der Funktionen und einem Argument ausgeführt und zu einer Liste zusammengefasst.

Man kann applikative Funktoren als Berechnungen in einem gewissen „Kontext“ ansehen: Dass z.B. eine Rechnung scheitern kann (Maybe, Either), dass eine Zustand verändert wird (State, IO) oder wie in unserem Fall mit Listen, dass es für eine Rechnung „mehrere Lösungen“ (wie etwa Nullstellen eines Polynoms) gibt, und diese Mehrdeutigkeit sozusagen mitgeschleift wird.

Hier nun die Umsetzung in highJ:

import fj.F;
import fj.data.List;
import highj._;
import highj.data.ListMonadPlus;
import highj.data.ListOf;
import highj.typeclasses.category.Applicative;

...

Applicative<ListOf> app = ListMonadPlus.getInstance();

F<_<ListOf,Boolean>, Boolean> and = new F<_<ListOf,Boolean>, Boolean>() {
     public Boolean f(_<ListOf, Boolean> wrappedList) {
        for(Boolean b : ListOf.unwrap(wrappedList)) 
            if (! b) return false;
        return true;
    }
};

F<Integer, Boolean> greaterThanTwo = new F<Integer, Boolean>() {
    public Boolean f(Integer a) { return a > 2; }
};

F<Integer, Boolean> lessThanSeven = new F<Integer, Boolean>() {
    public Boolean f(Integer a) { return a < 7; }
};

F<Integer, Boolean> odd = new F<Integer, Boolean>() {
    public Boolean f(Integer a) { return a % 2 == 1; }
};

_<ListOf, F<Integer, Boolean>> condList = 
        ListOf.list(greaterThanTwo, lessThanSeven, odd); 

List<Integer> result = List.range(1, 10).filter(
    app.<Integer>pure().andThen(app.ap(condList).andThen(and)));
//[3, 5]

Wer sich die Imports genau angesehen hat, wird feststellen, dass nicht nur die Klasse F für Funktionen, sondern auch die Klasse List aus der Functional-Java-Bibliothek stammt. Es handelt sich um eine unveränderliche, einfach verlinkte Liste, die sich (bis auf die Lazyness) ähnlich wie eine Haskell-Liste verhält, also hier besser als java.util.List geeignet ist.

Als erstes besorge ich mir den applikativen Funktor für Listen (ListMonadPlus implementiert zahlreiche Interfaces, darunter auch dieses). Wie im letzen Artikel ausgeführt, benötige ich eine Wrapper-Klasse ohne Typ-Parameter für meine Typklassen, hier ListOf. Danach definiere ich die Funktionen, die ich benötige. Natürlich ist dieser Teil sehr viel länger als in Haskell, und macht die Anwendung recht unhandlich. Ich kann mir aber vorstellen, dass das in bestimmten Fällen noch „erträglich“ ist, nämlich dann, wenn es eine recht übersichtliche Anzahl von Funktionen für ein bestimmtes Gebiet gibt, die man alle nur einmal irgendwo definieren und dann beliebig kombinieren kann. Und Java 8 verspricht hier mit der Lambda-Syntax Abhilfe.

Als nächstes packe ich meine drei Kriterien in eine Liste. Dann kann endlich der Aufruf erfolgen, und zwar fast so wie in Haskell. Die range- und andThen-Methoden stammen aus Functional Java, wobei letztere die beiden aus dem applikativen Funktor stammende pure- und ap- sowie die lokale and-Funktion miteinander kombiniert (oder genauer gesagt komponiert).

Natürlich war das nur ein Zipfelchen dessen, was möglich ist. Wie immer lohnt es nicht, mit so einer Framework-Kanone auf einen Beispiel-Spatzen zu schießen, sondern die Typklassen müssten schon einen wesentlichen Teil der Problem-Domäne abdecken, um einen Einsatz zu rechtfertigen. Dabei gibt es wirklich interessante Sachen: Neulich habe etwa ich ein Papier über die Anwendung von Biarrows gelesen, wodurch reversible Programmierung ermöglicht wird (wenn man z.B. mit dieser Technik einen Parser für eine Sprache schreibt, kann man damit auch „automatisch“ aus einem Syntaxbaum wieder Sourcecode erstellen). Vielleicht wäre highJ für solche oder ähnliche Projekte eine nützliche Ausgangsbasis.

Bevor ich es vergesse: Ich bitte um etwas Geduld, es wird hier auch bald wieder um Scala gehen. Das highJ-Experiment schlägt (auf Kosten der Praktikabilität) eine weite Brücke von Haskell nach Java, und ich denke, dass ich daraus einiges lernen kann – auch in Hinblick auf meine Fähigkeiten in Scala.

Advertisements

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