Na początek małe wprowadzenie teoretyczne. Istnieją funkcje, których dziedzina jest w jakiś sposób ograniczona do pod zbioru obiektów należących do danego typu. Najlepszym przykładem jest dzielenie (dla uproszczenia liczb całkowitych), które działa o ile dzielnik nie jest zerem. Wynik takiej operacji jest nieokreślony, albo najzwyczajniej błędny. Jeżeli przenosimy taką funkcję do kodu to możemy:

  • Stworzyć (pod)typ, który będzie zawierać tylko elementy należące do dziedziny.
  • Wyrzucić wyjątek.

Pierwsze rozwiązanie jest czasami stosowane w niektórych językach. Wymaga jednak bardzo dobrze przemyślanego systemu typów, który dopuszcza m.in. wielodziedziczenie czy też niejawną konwersję argumentów. Drugie podejście jest znacznie prostsze i w pewnym sensie mniej w nim magii.

Co, jednak jeżeli chcemy, by funkcja nie waliła nam wyjątkami? Ewentualnie mamy na wejściu funkcję, jakąś, i nie wiemy, czy po jej wywołaniu z podanymi parametrami nie nastąpi wybuch?

Lifting funkcji

Dokładny opis działania mechanizmy znajdziecie na wikiW. Ja pokażę, jak działa lifting w Javaslang.

Listing 1. Bezpieczne dzielenie

public void exampleLifting() {
    Function2<Integer, Integer, Integer> div = (a, b) -> a / b;
    Function2<Integer, Integer, Option<Integer>> saveDiv = Function2.lift(div);

    System.out.println(saveDiv.apply(1, 0));
}

Nasza pierwotna funkcja div wywali się, gdy tylko b będzie równe 0. Jednak jeżeli użyjemy metody lift, by wynik opakować w Optional, to wywołanie z niepoprawnym parametrem zwróci None. Program się nie wywali.

Podsumowanie

Procedura liftingu, która jest dostępna w Javaslang, choć nie jest w pełni zgodna z matematyczną definicją, to stanowi bardzo dobre narzędzie zabezpieczające. Pozwala nie tyle, co na walidację argumentu wejściowego w postaci funkcji, ale na sanityzacjęW parametrów wejściowych, które są funkcjami. Dzięki temu mamy pewność, że funkcje, które otrzymujemy, nie popsują nam kodu w nieprzewidywalny sposób.

Wadą tego rozwiązania jest konieczność stosowania funkcji z API Javaslang, a nie funkcji z podstawowego API Javy 8. Choć zawsze można…

Listing 2. Implementacja lift dla BiFunction

public <T, U, R> BiFunction<T, U, Option<R>> lift(BiFunction<T, U, R> f) {
    return (t, u) -> Try.of(()-> f.apply(t, u)).getOption();
}