Kleiner Trick mit Optional.or()

Angenommen, ich habe ein paar Java-Methoden, die Optionals verschiedener Typen zurückliefen:

Optional<Integer> findInt(String s) {
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (Exception ex) {
        return Optional.empty();
    }
}

Optional<Long> findLong(String s) {
    try {
        return Optional.of(Long.parseLong(s));
    } catch (Exception ex) {
        return Optional.empty();
    }
}

Optional<Double> findDouble(String s) {
    try {
        return Optional.of(Double.parseDouble(s));
    } catch (Exception ex) {
        return Optional.empty();
    }
}

Wenn man nun versucht, die Ergebnisse dieser Methoden mit or() zu verknüpfen, um den ersten „Treffer“ zu ermitteln, schlägt das fehl:

String s = "12.3";
//compiliert nicht:
Optional<Number> n = findInt(s)
    .or(() -> findLong(s))
    .or(() -> findDouble(s));

Das passiert deshalb, weil durch den ersten Aufruf der Typ des Optionals „zu eng“ auf Optional<Integer> eingegrenzt worden ist.

Nun gibt es mehrere Möglichkeiten, das Problem zu lösen, aber die einfachste, die ich finden konnte ist, die Optional-Kette mit einem leeren, aber richtig getypten Optional starten zu lassen:

String s = "12.3";
Optional<Number> n = Optional.<Number>empty()
    .or(() -> findInt(s))
    .or(() -> findLong(s))
    .or(() -> findDouble(s));

Das war’s schon. Und wie immer: Wenn jemand eine bessere Lösung findet, nur her damit…

Pattern Matching für Kotlin

Ich habe einen kleine Bibliothek zum Pattern-Matching in Kotlin geschrieben: https://github.com/DanielGronau/kopama

Die Idee ist, Pattern Matching in when-Ausdrücken zu ermöglichen. Die Syntax dazu ist ein wenig an Hamcrest Matcher angelehnt. Dazu kommen verschiedene Möglichkeiten, Objekte – insbesondere Collections und Data-Klassen – in ihre Bestandteile zu zerlegen. Hier ein einfaches Beispiel:

val p = Person("John", "Doe", 34)

when(match(p)) {
   Person::class(any, any, le(18)) -> println("Eine minderjährige Person")
   Person::class("John", "Doe", any) -> println("Es ist John Doe")
   (eq("Alice") or eq("Bob"))[Person::firstName] -> println("Es ist Alice oder Bob")
   else -> println("unbekannte Person")
}

Es gibt natürlich noch viele andere Patterns, von Null-Tests über Vergleiche bis zu Tests für Collections. Für komplizierte Zugriffspfade zu etwas versteckten Informationen kann man auch Lambdas verwenden. Es ist ebenfalls möglich, Werte zu zwischenzuspeichern und dann auf der rechten Seite wieder auszulesen – auch wenn die Syntax etwas umständlich ist. Pattern selbst sind nicht viel mehr als einfache Prädikate, es ist also auch nicht schwierig, seine eigenen zu schreiben.

Ein Problem der Bibliothek, das nicht wirklich zu vermeiden ist, ist die mangelnde Typsicherheit. Da das Zerlegen in einzelne Bestandteile über Reflection erfolgt, kann der zu erwartende Typ nicht korrekt vorherbestimmt werden. Deshalb kann man auch Pattern schreiben, die nicht sehr sinnvoll sind, etwa einen Vergleich von einem String mit einer Zahl. In solchen Fällen schlägt der Test fehl, wirft aber keinen Fehler. Im Endeffekt hilft hier nur Disziplin, Tests und etwas Augenmaß dafür, was eventuell schiefgehen kann.

Ich habe sehr viel bei der Arbeit an der Bibliothek gelernt, und es hat viel Spaß gemacht, das DSL zu designen. Für Vorschläge und Kritik bin ich immer dankbar.

Zählender Builder in Java

Ich hatte schon einen Artikel darüber geschrieben, wie man in Kotlin einen „zählenden“ Builder implementieren kann: https://dgronau.wordpress.com/2018/05/27/der-builder-der-zaehlen-konnte/

Mit etwas mehr Aufwand ist das in Java auch möglich, aber diesmal nicht über Generics, sondern über etwas, was ich „Chamäleon-Pattern“ nenne: Eine Klasse gibt sich nach außen nur jeweils als eines ihrer Interfaces zu erkennen, was einschränkt, was der Nutzer mit ihr tun kann (wenn er nicht mit Casts mogelt). Zuerst einmal die Zielklasse, wie im ursprünglichen Artikel ein Polygon:

import java.awt.Point;
import java.util.List;

public class Polygon {
    private final List<Point> points;

    public Polygon(List<Point> points) {
        this.points = points;
    }

    public List<Point> getPoints() {
        return points;
    }
}

Jetzt kommt die ganze Latte an Interfaces, die das Verhalten beschreibt, nämlich dass ein „richtiges“ Polygon wenigstens drei Punkte enthalten sollte:

public interface PB0 {
    PB1 add(Point point);
}

public interface PB1 {
    PB2 add(Point point);
}

public interface PB2 {
    PB3Plus add(Point point);
}

public interface PB3Plus {
    PB3Plus add(Point point);
    Polygon build();
}

Nur das letzte Interface hat eine Build-Methode. Werden zusätzliche, optionale Attribute (hier etwa Farbe oder Strichdicke) gewünscht, müsste man die auch in allen Interfaces erwähnen. Am Ende kommt nun der Builder selbst, der tatsächlich mit einer einzigen Add-Methode auskommt, die trotzdem alle Interface-Varianten implementiert, dank der Regel, dass covariante Rückgabetypen erlaubt sind:

public class PolygonBuilder implements PB0, PB1, PB2, PB3Plus {

  private final List<Point> points = new ArrayList<>();

  private PolygonBuilder() {}

  public PolygonBuilder add(Point point) {
    points.add(point);
    return this;
  }

  public Polygon build() {
    return new Polygon(points);
  }

  public static PB0 builder() {
    return new PolygonBuilder();
  }
}

Natürlich darf man dem Nutzer nicht erlauben, selbst einen PolygonBuilder zu konstruieren, der Konstruktor muss privat sein. Stattdessen wird über die Builder-Methode ein PB0-Interface zurückgeliefert. Dadurch ist die Build-Methode erst einmal „unsichtbar“ (wie gesagt, wenn der Nutzer nicht schummelt und castet).

Ich weiß nicht, wie gut diese Lösung in der Praxis funktioniert, wahrscheinlich wäre für einen ernsthafte Anwendung ein Code-Generator eine gute Idee. Und natürlich hat man das Problem, dass man auf diese Weise nur ein Attribut „zählen“ kann, eine zweite Liste würde zu einer kombinatorischen Explosion führen. Aber für einfache Anwendungsfälle wie im obigen Beispiel hat diese Lösung durchaus ihren Charm.

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<Point2D>)

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

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.

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()

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.