Do-Notation in Java 8+

Ich habe eine kleine Bibliothek geschrieben, die ein DSL für etwas Ähnliches wie Haskell’s Do-Notation oder Scala’s For-Comprehension in Java 8+ bereitstellt:

doBlocks

Alles noch sehr experimentell, und jeder einzelne Zieltyp muss separat unterstützt werden. Die Syntax sieht ungefähr so aus (mal an einem Nonsense-Beispiel demonstriert):

Optional<Integer> optA = ...
Optional<Integer> optB = ...
Optional<Integer> optC = ...

Optional<String> answer = doBlock(ctx -> {
   Val<Integer> $a = ctx.bind(optA);
   Val<Integer> $b = ctx.bind(optB);
   Val<Integer> $c = ctx.bind(optC);
   Val<Integer> $maxAB = ctx.bind($a, $b, Math::max); 
   Val<Integer> $maxABC = ctx.bind($maxAB, $c, Math::max); 
   Val<String>  $s = ctx.let($maxABC, maxABC -> "Das Maximum ist " + maxABC);
   return $s;
});

Für Anregungen bin ich immer dankbar.

Optional im Quadrat

Ich wünschte wirklich, dass es in Java mehr Unterstützung für „monadisches Zeug“ im Doppelpack gäbe. Angefangen von Pair, zu Optionals, und bis zu Streams. Pair ist natürlich langweilig, und x-mal verfügbar, aber hier mal ein Schnellschuss, wie ich mir eine Helferklasse für zwei Optionals vorstellen würde:

import java.util.Objects;
import java.util.Optional;
import java.util.function.*;

public class Optional2<A, B> {

    private final Optional<A> first;
    private final Optional<B> second;

    private Optional2(Optional<A> first, Optional<B> second) {
        this.first = Objects.requireNonNull(first);
        this.second = Objects.requireNonNull(second);
    }

    public static <A, B> Optional2<A, B> empty() {
        return of(Optional.empty(), Optional.empty());
    }

    public static <A, B> Optional2<A, B> of(Optional<A> first, Optional<B> second) {
        return new Optional2<>(first, second);
    }

    public static <A, B> Optional2<A, B> ofNullable(A a, B b) {
        return new Optional2<>(Optional.ofNullable(a), Optional.ofNullable(b));
    }

    public static <A> Optional<A> unify(Optional2<A, A> optional2, BinaryOperator<A> op) {
        return unify(optional2.first, optional2.second, op);
    }

    public static <A> Optional<A> unify(Optional<A> first, Optional<A> second, BinaryOperator<A> op) {
        return first.map(
            a -> second.map(
                b -> op.apply(a, b)).orElse(a))
                    .map(Optional::of)
                    .orElse(second);
    }

    public Optional<A> first() {
        return first;
    }

    public Optional<B> second() {
        return second;
    }

    public Optional2<B, A> swap() {
        return of(second, first);
    }

    public <C> Optional<C> map(BiFunction<A, B, C> fn) {
        return first.flatMap(
            a -> second.map(
                b -> fn.apply(a, b)));
    }

    public <C> Optional2<C, B> mapFirst(BiFunction<A, B, C> fn) {
        return of(map(fn), second);
    }

    public <C> Optional2<C, B> mapFirst(Function<A, C> fn) {
        return of(first.map(fn), second);
    }

    public <C> Optional2<A, C> mapSecond(BiFunction<A, B, C> fn) {
        return of(first, map(fn));
    }

    public <C> Optional2<A, C> mapSecond(Function<B, C> fn) {
        return of(first, second.map(fn));
    }

    public <C> Optional<C> flatMap(BiFunction<A, B, Optional<C>> fn) {
        return first.flatMap(
            a -> second.flatMap(
                b -> fn.apply(a, b)));
    }

    public <C> Optional2<C, B> flatMapFirst(BiFunction<A, B, Optional<C>> fn) {
        return Optional2.of(flatMap(fn), second);
    }

    public <C> Optional2<C, B> flatMapFirst(Function<A, Optional<C>> fn) {
        return Optional2.of(first.flatMap(fn), second);
    }

    public <C> Optional2<A, C> flatMapSecond(BiFunction<A, B, Optional<C>> fn) {
        return Optional2.of(first, flatMap(fn));
    }

    public <C> Optional2<A, C> flatMapSecond(Function<B, Optional<C>> fn) {
        return Optional2.of(first, second.flatMap(fn));
    }

    public <C, D> Optional2<C, D> biFlatMap(BiFunction<A, B, Optional2<C, D>> fn) {
        return first.flatMap(
            a -> second.map(
                b -> fn.apply(a, b)
            )).orElseGet(Optional2::empty);
    }

    public Optional2<A, B> filter(BiPredicate<A, B> predicate) {
        return map(predicate::test).orElse(false) ? this
                   : Optional2.of(Optional.empty(), Optional.empty());
    }

    public Optional2<A, B> filterFirst(BiPredicate<A, B> predicate) {
        return map(predicate::test).orElse(false) ? this
                   : Optional2.of(Optional.empty(), second);
    }

    public Optional2<A, B> filterFirst(Predicate<A> predicate) {
        return Optional2.of(first.filter(predicate), second);
    }

    public Optional2<A, B> filterSecond(BiPredicate<A, B> predicate) {
        return map(predicate::test).orElse(false) ? this
                   : Optional2.of(first, Optional.empty());
    }

    public Optional2<A, B> filterSecond(Predicate<B> predicate) {
        return Optional2.of(first, second.filter(predicate));
    }

    public boolean bothPresent() {
        return first.isPresent() && second.isPresent();
    }

    public boolean bothEmpty() {
        return !first.isPresent() && !second.isPresent();
    }

    public Optional2<A, B> ifBothPresent(BiConsumer<A, B> consumer) {
        first.ifPresent(
            a -> second.ifPresent(
                b -> consumer.accept(a, b)));
        return this;
    }

    public Optional2<A, B> ifBothEmpty(Runnable runnable) {
        if (!first.isPresent() && !second.isPresent()) {
            runnable.run();
        }
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Optional2<?, ?> optional2 = (Optional2<?, ?>) o;
        return first.equals(optional2.first) &&
                   second.equals(optional2.second);
    }

    @Override
    public int hashCode() {
        return Objects.hash(first, second);
    }

    @Override
    public String toString() {
        return String.format("Optional2[%s,%s]",
            first.map(Object::toString).orElse("<empty>"),
            second.map(Object::toString).orElse("<empty>"));
    }
}

Die Benutzung sollte ziemlich selbsterklärend sein, deshalb spare ich mir Beispiele. Ansonsten ist nur noch zu erwähnen, dass ich stark in Versuchung war, die Klasse „Dopptional“ zu nennen – aber der Pun ist selbst für meine Verhältnisse zu mies…

Der Code als Gist

Die if-Kaskade und „übertragbarer“ Code

Jeder von uns hat schon solchen Code geschrieben:

if (bmi >= 40) {
    System.out.println("Serious obesity");
} else if (bmi >= 30) {
    System.out.println("Obesity");
} else if (bmi >= 25) {
    System.out.println("Overweight");
} else if (bmi >= 18) {
    System.out.println("Standard");
} else {
    System.out.println("Underweight");
}

Ja, ich weiß dass der BMI ein schlechter Maßstab für Über- oder Untergewicht ist. Davon abgesehen ist es unvermeidlich, dass man als Anfänger solchen Code schreibt. Aber dieses Code-Beispiel hat mich zum Nachdenken angeregt. Warum ist dieser Code so schlecht? Die einfache Antwort ist, dass er sowohl das Single Responisbility Principle verletzt, als auch eine Code-Duplizierung darstellt, also gegen das DRY-Prinzip verstösst (nämlich in Form von fünf System.out.println-Aufrufen). Der Code tut zwei Dinge auf einmal: Er trifft eine Fallunterscheidung, und er gibt etwas auf der Konsole aus. Beheben wir das:

String result;
if (bmi >= 40) {
    result = "Serious obesity";
} else if (bmi >= 30) {
    result = "Obesity";
} else if (bmi >= 25) {
    result = "Overweight";
} else if (bmi >= 18) {
    result = "Standard";
} else {
    result = "Underweight";
}
System.out.println(result);

Schon besser, aber die Fallunterscheidung könnte auch anderswo nützlich sein, packen wir sie in eine Methode:

public static String evaluate(double bmi) {
    if (bmi >= 40) {
        return "Serious obesity";
    } else if (bmi >= 30) {
        return "Obesity";
    } else if (bmi >= 25) {
        return "Overweight";
    } else if (bmi >= 18) {
        return "Standard";
    } else {
        return "Underweight";
    }
}
...
System.out.println(evaluate(bmi));

Jetzt ist aber alles gut, oder nicht? Kommt darauf an, für manche Anwendungsfälle mag es ausreichend sein, aber ich sehe immer noch zwei(!) Probleme.

Das erste Problem ist Skalierbarkeit. Was ist, wenn man mehr Fallunterscheidungen treffen will? Oder wenn man die Fälle z.B. aus einer Datenbank oder Properties-Datei laden will?

Das zweite Problem – das, was mich zum Nachdenken gebracht hat – geht in eine ähnliche Richtung, ist aber etwas schwerer zu fassen: Der Code ist nicht „übertragbar“. Ich kann die Fallunterscheidungen nicht leicht durch meine Applikation transportieren, sie sind duch eine Methode eng an eine bestimmte Klasse gekoppelt. Wäre es nicht besser, wir würden statt einer Methode eine Datenstruktur schreiben, die die Fallunterscheidung kapselt? Könnte es vielleicht sogar sein, dass es in Java solch eine Datenstruktur schon gibt?

Ja, es gibt diese Datenstruktur bereits, und sie nennt sich TreeMap (oder jede andere Map, die das Interface NavigableMap implementiert):

private final static NavigableMap<Double, String> CATEGORIES = categories();

public static NavigableMap<Double, String> categories() {
    NavigableMap<Double, String> map = new TreeMap<>(); 
    map.put(40.0, "Serious obesity");
    map.put(30.0, "Obesity");
    map.put(25.0, "Overweight");
    map.put(18.0, "Standard");
    map.put(0.0, "Underweight");
    return map;
}
...
public static String evaluate(NavigableMap<Double, String> categories, double bmi) {
    return categories.floorEntry(bmi).getValue()
}
...
System.out.println(evaluate(CATEGORIES, bmi)); 

Natürlich kommt es ganz auf den Anwendungsfall an, wie und wo man die TreeMap initialisiert. Der Punkt ist aber, dass man sie leicht in verschiedener Weise füllen kann (Skalierbarkeit) und sie dann auch frei durch die Anwendung hindurchschleusen kann. Statt sie wie hier statisch zu erzeugen, könnte sie genauso als Konstruktor-Argument übergeben werden u.s.w. Als Datenstruktur besitzt sie eine Art von Mobilität, die eine Methode nicht ohne weiteres bieten kann.

Wie schon gesagt muss man abwägen, ob man die zusätzliche Flexibilität im gegebenen Kontext wirklich braucht, oder ob es schon in Overengineering ausartet. Aber man muss die Möglichkeit des weitergehenden Refactorings erst einmal sehen, um eine qualifizierte Entscheidung fällen zu können. Das Beispiel zeigt auf jeden Fall, dass es sich lohnen kann, auch bei „einfachem“ Code genauer hinzuschauen.

Ein Klassen-Matcher in Java

Es ist unglaublich, aber ich habe tatsächlich noch keinen Klassen-Matcher in meinem Blog behandelt.

Wenn man öfter mal basierend auf dem Typ eines Wertes verzweigen muss, sieht es oft so aus:

Object obj = ...
String s;
if (obj instanceof Integer) {
    s = "int " + (Integer) obj;
} else if (obj instanceof Long) {
    s = "long " + (Long) obj;
} else if (obj instanceof Double) {
    s = "double" + (Double) obj;
} else {
    s = "häh?!?";
}

Oder noch schlimmer, wenn man sich „sicher“ ist, dass es nur zwei Alternativen gibt:

String s = obj instanceof Integer 
    ? "int " + (Integer) obj
    : "long " + (Long) obj; 

…was natürlich mit einem niedlichen kleinen Atompilz explodiert, wenn dann doch etwas anderes in obj steckt.

Wie wäre es dagegen mit dieser Syntax hier:

String s = match(obj,
            when(Integer.class, i -> "int " + i),
            when(Long.class, l -> "long " + l),
            when(Double.class, d -> "double " + d)
        ).orElse("häh?!?");

Die match-Methode liefert ein Optional, so dass man leicht selbst entscheiden kann, wie man auf einen unbekannten Typ reagiert. Die Argumente in den Lambdas sind natürlich typsicher, das i ist z.B. vom Typ Integer. Im Ausgangsbeispiel hat man ein Problem, wenn instanceof-Check und Typcast nicht zusammenpassen – das kann hier nicht passieren.

Die DSL-Klasse, die das ermöglicht, ist erstaunlich kurz:

import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;

public final class InstanceOf {

    @SafeVarargs
    public static <R> Optional<R> match(Object o, Function<Object, Optional<R>>... cases) {
        return Arrays.stream(cases)
                     .map(c -> c.apply(o))
                     .filter(Optional::isPresent)
                     .findFirst()
                     .flatMap(Function.identity());
    }

    public static <T, R> Function<Object, Optional<R>> when(Class<T> clazz, Function<T, R> fn) {
        return obj -> clazz.isAssignableFrom(obj.getClass())
                        ? Optional.of(fn.apply((T) obj))
                        : Optional.empty();
    }
}

Eigentlich eine Schande, dass man so etwas nicht gleich in Java 8 eingebaut hat.

Dann wünsche ich viel Spaß beim typsicheren Kaskadieren!

Der Builder, der zählen konnte

In letzter Zeit habe ich mit Kotlin herumexperimentiert und bin begeistert, welche DSLs damit möglich sind. Das typsichere Builder-Pattern hatte ich hier schon öfter diskutiert, heute soll es zählen lernen. Eine Polygon-Klasse ist ein guter Kandidat für eine Beispielanwendung, denn so richtig „polygon“ ist es erst, wenn es mindestens drei Punkte hat. Starten wir also mit einer entsprechenden Klasse:

import javafx.geometry.Point2D
import javafx.scene.paint.Color

data class Polygon(val color: Color, val points: List)

Wir werden uns jetzt einen Builder schreiben, der dieses DSL erlaubt:

val polygon = builder()
            .withFirstPoint(3.0, 4.0)
            .withColor(Color.AQUA)
            .withSecondPoint(4.0, 7.0)
            .withThirdPoint(1.0, 3.0)
            .withPoint(6.0, 7.5)
            .withPoint(9.0, 2.0)
            .build()

Die Farbe kann an beliebiger Stelle gesetzt werden, und ist erforderlich. Der Aufruf von build() ist auch erst dann möglich, wenn mindestens drei Punkte gesetzt sind. Um die unterschiedliche Benennung der with???Point-Methoden kommen wir leider nicht herum, denn gegen Type-Erasure ist auch Kotlin machtlos.

Für die Farb-Variable benötigen wir ein Interface, wie wir es bereits von den vorigen Beispielen kennen:

interface Value<T>

data class With<T>(val value: T) : Value<T>

class Without<T> : Value<T>

Das Zählen der Punkte übernimmt ein weiteres Interface:

interface Counted

interface None : Counted

interface One : Counted

interface Two : Counted

interface ThreeOrMore : Counted

Nun können wir die Builder-Klasse schreiben:

class Builder<out V : Value<Color>, C : Counted>(val color: V, val points: List<Point2D>) {
    fun withColor(c: Color) = Builder<With<Color>, C>(With(c), points)
}

Moment mal, das ist ein bisschen wenig, oder? Stimmt, die geheime Soße fehlt noch, und zwar in Form von Extension-Methoden:

fun builder() = Builder<Without<Color>, None>(Without(), listOf())

fun <V : Value<Color>> Builder<V, None>.withFirstPoint(x: Double, y: Double) =
        Builder<V, One>(this.color, this.points + Point2D(x, y))

fun <V : Value<Color>> Builder<V, One>.withSecondPoint(x: Double, y: Double) =
        Builder<V, Two>(this.color, this.points + Point2D(x, y))

fun <V : Value<Color>> Builder<V, Two>.withThirdPoint(x: Double, y: Double) =
        Builder<V, ThreeOrMore>(this.color, this.points + Point2D(x, y))

fun <V : Value<Color>> Builder<V, ThreeOrMore>.withPoint(x: Double, y: Double) =
        Builder<V, ThreeOrMore>(this.color, this.points + Point2D(x, y))

fun Builder<With<Color>, ThreeOrMore>.build() = 
        Polygon(this.color.value, this.points)

Man kann sehr schön sehen, wie hier „mitgezählt“ wird. Natürlich wäre es noch schöner, wenn man alle Methoden withPoint nennen könnte, aber wie gesagt macht uns hier Type Erasure einen Strich durch die Rechnung.

Einen Schönheitsfehler hat das Ganze noch: Entgegen der Dokumentation war es mir nicht möglich, den Konstruktor von Builder private zu machen, die Extension-Methoden bekamen keinen Zugriff darauf, obwohl sie in der gleichen Datei standen.

Type-Level-Programming-Techniken wie die hier gezeigten sind nicht nur für Builder, sondern für alle möglichen DSLs interessant. Counted ist ein sogenannter Phantom-Typ, der niemals reale Werte hat, sondern nur dazu dient, zusätzliche „Garantien“ für einen Typ zu codieren. So könnte man SQL-Abfragen, die von einem Client kommen, mit einem Phantom-Typ versehen, der anzeigt, ob man sie schon auf SQL-Injection-Versuche getestet hat oder nicht – und dann kann man diesen Test nicht mehr vergessen, egal wo solche Werte im System herumschwirren.

So, das war jetzt nach – ahäm – längerer Pause endlich wieder mal ein Beitrag, und es hat Spaß gemacht, ihn zu schreiben. Ich denke, ich sollte wieder ein bisschen regelmäßiger bloggen…

Doppel-Switch in Java

Ich hasse es, wenn ich geschachtelte switch-Blöcke sehe. Es ist einfach unübersichtlich, und während man Fälle zusammenfassen kann, wo einem der zweite, „innere“ Wert egal ist, geht das für den ersten, „äußeren“ Wert nicht.

Nehmen wir als Anwendungsfall einmal ein boolesches TriState-Enum, das auch „und“- und „oder“-Operationen unterstützt. Eine mögliche Implementierung könnte so aussehen:

public enum TriState {
    TRUE, FALSE, UNKNOWN;

    private static TriState and(TriState a, TriState b) {
        switch(a) {
            case FALSE: return FALSE;
            case TRUE: return b;
            case UNKNOWN: switch(b) {
                case FALSE: return FALSE;
                default: return UNKNOWN;
            }
        }
        throw new AssertionError();
    }

    private static TriState or(TriState a, TriState b) {
        switch(a) {
            case TRUE: return TRUE;
            case FALSE: return b;
            case UNKNOWN: switch(b) {
                case TRUE: return TRUE;
                default: return UNKNOWN;
            }
        }
        throw new AssertionError();
    }
}

Man kann sich leicht vorstellen, wie bei mehr Werten die switches schnell unübersichtlich werden. Hier ein Beispiel, wie es mit DSL aussehen könnte:

public enum TriState {TRUE, FALSE, UNKNOWN;

    private static TriState and(TriState a, TriState b) {
        return switch2(a, b,
                case2(TRUE, TRUE, () -> TRUE),
                case2(FALSE, any(), () -> FALSE),
                case2(any(), FALSE, () -> FALSE),
                default2(() -> UNKNOWN)
        );
    }

    private static TriState or(TriState a, TriState b) {
            return switch2(a, b,
                    case2(FALSE, FALSE, () -> FALSE),
                    case2(TRUE, any(), () -> TRUE),
                    case2(any(), TRUE, () -> TRUE),
                    default2(() -> UNKNOWN)
            );
    }
}

Ich habe mich entschlossen, die „Leerstellen“ über einen speziellen Wert any() zu kennzeichnen. Wenn beide Werte egal sind, habe ich analog zum normalen switch als Synonym auch die Methode default2 bereitgestellt. Ich hoffe, dass das die Bedienung intuitiver macht. Hier die Implementierung des DSLs:

import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Supplier;

public final class Switch {
    private Switch(){ /*do not instantiate*/ }

    enum Any {any}

    public static Any any() {
        return Any.any;
    }

    @SafeVarargs
    public static <A,B,R> R switch2(A a, B b, BiFunction<A, B, Optional<R>>... cases) {
        return Arrays.stream(cases)
            .map(biFunction -> biFunction.apply(a,b))
            .flatMap(opt -> opt.map(Stream::of).orElseGet(Stream::empty))
            .findFirst()
            .orElseThrow(() -> new RuntimeException("no match found"));
    }

    public static <A,B,R> BiFunction<A, B, Optional<R>> case2(A a, B b, Supplier<R> result) {
        return (_a, _b) -> a.equals(_a) && b.equals(_b) ? Optional.of(result.get()) : Optional.empty();
    }

    public static <A,B,R> BiFunction<A, B, Optional<R>> case2(A a, Any any, Supplier<R> result) {
        return (_a, _b) -> a.equals(_a) ? Optional.of(result.get()) : Optional.empty();
    }

    public static <A,B,R> BiFunction<A, B, Optional<R>> case2(Any any, B b, Supplier<R> result) {
        return (_a, _b) -> b.equals(_b) ? Optional.of(result.get()) : Optional.empty();
    }

    public static <A,B,R> BiFunction<A, B, Optional<R>> case2(Any any1, Any any2, Supplier<R> result) {
        return default2(result);
    }

    public static <A,B,R> BiFunction<A, B, Optional<R>> default2(Supplier<R> result) {
        return (_a, _b) -> Optional.of(result.get());
    }
}

Auch wenn man in anderen Sprachen noch viel „natürlichere“ DSLs schreiben kann, ist es doch immer wieder schön zu sehen, wie „erweiterbar“ Java inzwischen geworden ist, und das ohne größeren Aufwand. Ich glaube aber, dass bei vielen Java-Entwicklern das Bewusstsein fehlt, welche Möglichkeiten sie inzwischen haben. Das ist schade, aber ich hoffe sehr, dass sich das langsam ändert.

Die 8 Damen in highJ

Normalerweise schreibe ich hier kaum über meine highJ-Bibliothek. Da ist auch wirklich ziemlich seltsames Zeug drin, und seit Clinton Selke mitmacht, auch immer mehr, was ich selbst nicht mehr so ganz verstehe.

Nun gab es vor kurzem die Anregung von Clinton, die MonadRec-Implementierung aus PureScript zu übersetzen, weil man damit Stacküberläufe verhindern kann, wenn man mit Monaden(-transformern) arbeitet. Heute nun habe ich meine Listen-Monade nun um besagte MonadRec erweitert, und mich gefragt, wie ich das ganze denn testen soll. Zu meiner eigenen Überraschung habe ich einen wohlbekannten Anwendungsfall gefunden.

Hier ist das Ergebnis:

public static List<String> eightQueens() {
    Predicate<List<Integer>> isValid = pos ->
        ! List.of(pos.tail(),
            List.zipWith(pos.tail(), List.range(1), x -> y -> x+y),
            List.zipWith(pos.tail(), List.range(1), x -> y -> x-y))
        .contains(list -> list.contains(pos.head()));

    Function<List<Integer>, _<List.µ, Either<List<Integer>,String>>> fn =
        pos -> pos.size() == 8
            ? List.of(Either.newRight(
                Strings.mkString("[", ",", "]", pos.reverse())))
            : List.range(1,1,8).map(pos::plus)
               .filter(isValid).map(Either::newLeft);

    return List.monadPlus.tailRec(fn, List.empty());
}

Wer zu meinem Beitrag Und wieder mal die Damen… springt, kann dort das eher bescheidene Ergebnis des titanischen Ringens mit der funktionalen API von Java 8 bewundern. Man merkt richtig, wie einem dort die geeigneten Werkzeuge fehlen, oder zumindest schwer erreichbar sind. Und das lässt den Code schwerfällig und plump erscheinen.

Mit dem obigen Schnipsel bin ich recht zufrieden. Sicher, es sieht erst einmal ungewöhlich aus. Und ich will hier auch gar nicht im Detail erklären, wie der Code funktioniert, das ist gar nicht der Punkt. Worauf ich hinaus will ist, dass das Beispiel zeigt, was selbst in den Grenzen von Java 8 möglich ist, wenn man sich auf eine entsprechende „Infrastruktur“ stützen kann. Es ist auch ein Beispiel, wie unmittelbar „theoretisches Zeug“ ganz praktische Probleme erleichtern kann.

Wer trotzdem eine „high-level“-Erklärung haben will: MonadRec ist an sich nichts besonderes, es „externalisiert“ nur den Prozess der Rekursion, und erlaubt so, selbige hinter den Kulissen durch Iteration zu ersetzen (was den allseits unbeliebten Stackoverflow verhindert). Die „geheime Soße“ ist nun, dass die Liste als Monade eben nicht nur ein schnöder Container ist, sondern auch als mehrwertiges Berechnungsergebnis gesehen werden kann. Damit führt man in den simplen Mechanismus von MonadRec die Möglichkeit ein, nicht nur eindimensional die Lösung zu suchen, sondern in vielen Richtungen, und damit einen Suchraum aufzuspannen. Der Witz ist, dass sich das „einfach so“ ergibt; es ist die naheliegende und natürliche Lösung, wenn man das MonadRec-Interface für die Listen-Monade implementieren will.

Natürlich ist und bleibt meine highJ-Bibliothek experimentell, und die API ist nicht stabil. Für die praktische funktionale Programmierung in Java würde ich deshalb eher zu javaslang oder functionaljava raten.

Eine fehlende Optional-Methode und ein Workaround

Ein wirklich hässliches Pattern, um mehrere Dinge auszuprobieren, bis eines davon klappt, ist:

int x = ...
int y = ...
int z = ...

Integer result = null;
if (x % 2 == 0) {
    result = x / 2;
}
if (result == null && y % 2 == 0) {
    result = y / 2;
}
if (result == null && z % 2 == 0) {
    result = z / 2;
}
String s = result == null
    ? "all numbers were odd"
    : "the result is " + result;

Eigentlich sollte Optional uns in diesen Fällen die Null-Checks abnehmen. In Scala und mit der Optional-Variante von Guava geht das auch sehr schön, da in beiden Fällen Methoden vorhanden sind, um einen vorhandenen Wert unverändert „durchzuschleusen“, aber im „leeren“ Fall stattdessen ein anderes Optional zu verwenden.

Unverständlicherweise fehlt eine solche Funktion in Java 8. Die direkte Übersetzung unseres Beispiels sieht deshalb auch nicht viel hübscher als das Original aus:

public static Optional<Integer> half(int x) {
    return x % 2 == 0 
        ? Optional.of(x / 2) 
        : Optional.empty();
}

...

Optional<Integer> result = half(x);
if (! result.isPresent()) {
    result = half(y);
}
if (! result.isPresent()) {
    result = half(z);
}
String s = result
    .map(n -> "the result is " + n)
    .orElse("all numbers were odd");

Zum Vergleich die elegante Scala-Version:

def half(n : Int) : Option[Int] = 
    if (n % 2 == 0) Some(n / 2) else None

val s = half(x)
    .orElse(half(y))
    .orElse(half(z))
    .map(n => "the result is " + n)
    .getOrElse("all numbers were odd")

Haben wir eine Chance, das wenigstens ungefähr so nachzubauen? Ja, es gibt einen Workaround. Das ist wieder eines jener Probleme, über das man durchaus eine Weile grübeln kann, aber wenn man einmal die Lösung kennt, erscheint sie ganz selbstverständlich:

String s = half(x).map(Optional::of)
    .orElse(half(y)).map(Optional::of)
    .orElse(half(z))
    .map(n -> "the result is " + n)
    .orElse("all numbers were odd");

Der Trick ist, mit einem map aus dem Optional<Integer> jeweils ein Optional<Optional<Integer>> zu machen, bevor wir mit der orElse-Methode wieder eine Optional-Ebene „abbauen“. Insgesamt macht diese map-orElse-Kombination also genau dasselbe, was in Scala ein einzelnes orElse (oder im Optional von Guava ein or) erledigt.

Wir haben sogar die Chance, hier noch etwas besser zu machen. Was wäre, wenn die half-Aufrufe lange dauern würden oder ungewünschte Seiteneffekte hätten? Dann sollten die Aufrufe natürlich lazy erfolgen, und das geht, in dem man das orElse einfach durch orElseGet ersetzt:

String s = half(x).map(Optional::of)
    .orElseGet(() -> half(y)).map(Optional::of)
    .orElseGet(() -> half(z))
    .map(n -> "the result is " + n)
    .orElse("all numbers were odd");

Trotzdem hoffe ich sehr, dass die Verantwortlichen ein Einsehen haben, Optional in Java 9 ein wenig komfortabler gestalten, und uns damit solche Verrenkungen in Zukunft ersparen.

Java-Kontrollstrukturen nachgebaut – if

Wieder so ein seltsamer Beitrag: Wozu sollte man Kontrollstrukturen nachbauen? Nun, selbst als reine Fingerübung lernt man einiges über die Möglichkeiten – und Unmöglichkeiten – der Sprache, aber die eigentliche Idee ist, einen soliden Ausgangspunkt für eigene Erweiterungen zu schaffen.

Beginnen wir mit einer der einfachsten Kontrollstrukturen, nämlich if, und dem verwandten ternären Operator x ? y : z. Das klassische if ist leicht zu modellieren – die einzige Schwierigkeit ist, dass wir schön „lazy“ bleiben, also einen Zweig nur dann ausführen, wenn die Entscheidungsvariable das auch vorsieht:

public static void if1(boolean choice, 
    Runnable ifTrue, 
    Runnable ifFalse) {
    if (choice) {
        ifTrue.run();
    } else {
        ifFalse.run();
    }
}

/Anwendung
if1(System.currentTimeMillis() % 2 == 0,
        () -> System.out.println("ifTrue"),
        () -> System.out.println("ifFalse"));

Allerdings wäre solcher Code in der funktionalen Programmierung verpönt, denn er tut nur eines: Seiteneffekte ausführen. Viel nützlicher wäre ein Verhalten, wie es der ternäre Operator zeigt, der stattdessen einen Wert zurückliefert. Nichts leichter als das:

public static <A> A if2(boolean choice, 
    Supplier<A> ifTrue, 
    Supplier<A> ifFalse) {
    return choice ? ifTrue.get() : ifFalse.get();
}

//Anwendung
String s = if2(System.currentTimeMillis() % 2 == 0,
    () -> "trueValue", 
    () -> "falseValue");

Wir haben jetzt schon einen kleinen Vorteil gegenüber dem ternären Operator: Die beiden Zweige können aus mehreren Operationen bestehen. Trotzdem geht es noch besser. Was ist z.B., wenn man das Konstrukt wiederverwenden will? Nun, mit einer kleine Änderung können wir die Auswertung „auf später“ verschieben, und damit eine Mehrfachnutzung erleichtern:

public static <A> Function<Boolean, A> if2Fun(
    Supplier<A> ifTrue, 
    Supplier<A> ifFalse) {
    return b -> if2(b, ifTrue, ifFalse);
}

//Anwendung
Function<Boolean, String> f = if2Fun(
    () -> "trueValue", 
    () -> "falseValue");
System.out.println(
    f.apply(System.currentTimeMillis() % 2 == 0));
System.out.println(
    f.apply(System.currentTimeMillis() % 2 == 0));

Ein anderer Schwachpunkt ist, dass sich unsere bisherigen Konstrukte schlecht schachteln lassen. Sicher sind ellenlange if-else-Kaskaden nicht die feine englische Art, aber hin und wieder lassen sie sich doch nicht vermeiden. Jetzt brauchen wir schon Fluent Interfaces, und bewegen uns langsam in Richtung DSL:

public class If3<A> {

    private Optional<A> result = Optional.empty();

    public static <A> If3<A> if3(boolean choice, 
        Supplier<A> ifTrue) {
        If3<A> if3 = new If3<>();
        if (choice) {
            if3.result = Optional.of(ifTrue.get());
        }
        return if3;
    }

    public If3<A> elseIf3(boolean choice, 
        Supplier<A> ifTrue) {
        if (! result.isPresent() && choice) {
           result = Optional.of(ifTrue.get());
        }
        return this;
    }

    public A else3(Supplier<A> ifTrue) {
        return result.orElseGet(ifTrue);
    }

}

//Anwendung
int i = 15;
String s = if3 (i < 10, () -> "kleiner 10")
    .elseIf3 (i < 100, () -> "kleiner 100")
    .else3(() -> "größer gleich 100");

Es ist auch möglich, die Variante, die eine Funktion liefert, und die letzte „kaskadierende“ Variante zu kombinieren, unter der Voraussetzung, dass alle Tests auf einen einzigen Wert ausgeführt werden können:

public class If4<T,A> {

    private Map<Predicate<T>, Supplier<A>> cases = 
        new LinkedHashMap<>();

    public static <T,A> If4<T,A> if4(
        Predicate<T> choice, 
        Supplier<A> ifTrue) {
        If4<T,A> if4 = new If4<>();
        if4.cases.put(choice, ifTrue);
        return if4;
    }

    public If4<T,A> elseIf4(Predicate<T> choice, 
        Supplier<A> ifTrue) {
        cases.put(choice, ifTrue);
        return this;
    }

    public Function<T, A> else4(Supplier<A> ifTrue) {
        return t -> {
            for(Map.Entry<Predicate<T>, Supplier<A>> entry : 
                cases.entrySet()) {
                if (entry.getKey().test(t)) {
                    return entry.getValue().get();
                }
            }
            return ifTrue.get();
        };
    }
}

//Anwendung
Function<Integer, String> f = if4(
        (Integer i) -> i < 10, () -> "kleiner 10")
        .elseIf4(i -> i < 100, () -> "kleiner 100")
        .else4(() -> "größer gleich 100");
System.out.println(f.apply(15));

Allerdings sieht man hier, dass damit die Grenzen von Javas Typinferenz-Mechanismus erreicht sind: Das Prädikat in der if4-Methode benötigt die Typangabe (Integer i), um zu kompilieren. Trotzdem ist es interessant, was alles möglich und – fast noch wichtiger – auch praktikabel ist.

Ich hoffe, die Zeit zu finden, diese kleine Serie fortzusetzen. Neben den offensichtlichen Kandidaten wie case und for dürfte auch try mit allen seinen Varianten (inklusive ARM) interessant sein.

Kleiner Optional-Trick in Java

So froh ich über die Einführung von Optional in Java 8 bin, so ärgerlich ist dort das Fehlen von Methoden, die eigentlich selbstverständlich sein sollten. So renne ich öfter in die Situation, dass ich aus dem Optional keinen Wert zurückliefern möchte, sondern abschließend eine Aktion ausführen. Dafür gibt es die Methode ifPresent, die einen Consumer entgegennimmt. Das Problem ist, dass ich im Falle eines leeren Optionals keine Default-Aktion angeben kann.

Zur Veranschaulichung ein Beispiel: Ich möchte aus einem Integer-Optional heraus auf die Konsole schreiben, ob die Zahl gerade ist oder nicht. Ist das Optional leer, will ich auch das auf die Konsole schreiben. Natürlich würde man in diesem einfachen Fall normalerweise den String „durchreichen“, aber nehmen wir an, dass statt der Textausgabe verschiedene Aktionen erforderlich sind, die sich nicht so leicht „integrieren“ lassen. Gewöhnlich sah der Code dann bei mir so aus:

Optional<Integer> op = ...
if (op.isPresent()) {
    op.ifPresent(i -> {
        System.out.println(i % 2 == 0 ? "even" : "odd");
    });
} else {
    System.out.println("empty");
}

Ich finde den expliziten Test recht hässlich – eine zusätzliche Schleife, die unser Gehirn drehen muss. Mein Workaround wird sicher auch keinen Schönheitspreis gewinnen, trotzdem halte ich diese Version für einen (kleinen) Fortschritt:

Optional<Integer> op = ...
op.<Runnable>map(i -> () -> {
    System.out.println(i % 2 == 0 ? "even" : "odd");
}).orElse(() -> System.out.println("empty")
).run();

Der Trick ist, die Aktion nicht sofort auszuführen, sondern in ein Runnable zu verpacken. Damit kann man dieses als Wert „durchschleifen“, und hat damit auch die Chance, über orElse auch einen Defaultwert angeben zu können. Die generische Typangabe bei map ist leider notwendig, und die Schreibweise mit den zwei Pfeilen in map ist auch gewöhnungsbedürftig. Man muss auch aufpassen, dass man das run() am Ende nicht vergisst.

Ich hoffe wirklich, dass hier bei Java 9 noch nachgebessert wird, denn solche Klimmzüge für ein einfaches Problem müssen nun wirklich nicht sein. Oder habe ich eine elegantere Lösung übersehen?