Pattern matching w Javie z Javaslang III
Poprzednie części:
Dopasowania wartości z pierwszej części oraz predykatów z drugiej, to nie jedyne możliwości, jakie daje nam Javaslang. Dziś przyjrzymy się wzorcom, czyli specjalnym obiektom, które opisują proces dekonstrukcji danego obiektu tak, by można było go wykorzystać w dopasowaniu. Poznaliśmy już wzorce, które mają znaczenie „każdy pasuje” – $(), oraz jest „równy w sensie equlas” – $(X). Standardowe API daje nam jednak kilka innych możliwości.
Sukces czy porażka
Mamy sobie kontener Try, o którym innym razem. Może mieć dwa stany – sukces (Success) i porażka (Fail). Jest to idealny kandydat do obsługi przez mechanizm dopasowań:
Listing 1. Dopasowanie Try.
public void successOrFailure() {
Try<Integer> success = Try.of(() -> 1);
Try<Integer> fail = Try.of(() -> {
throw new Exception("Ni huhu");
});
System.out.println(
Match(success).of(
Case(Success($()), "OK"),
Case(Failure($()), "NOK")
)
);
System.out.println(
Match(fail).of(
Case(Success($()), "OK"),
Case(Failure($()), "NOK")
)
);
}
Jako że Try ma tylko dwa stany to możemy pominąć warunek związany z dopasowaniem $() na końcu. Oczywiście możemy udoskonalić nasz kod, stosując bardziej precyzyjne dopasowania:
Listing 2. Dopasowanie Try z wykorzystaniem wzorców wewnętrznych.
public void successOrFailure() {
Try<Integer> success = Try.of(() -> 1);
Try<Integer> fail = Try.of(() -> {
throw new IllegalArgumentException("Ni huhu");
});
System.out.println(
Match(success).of(
Case(Success($(1)), "1 is OK"),
Case(Success($(i -> i == 2 )), "2 is OK too"),
Case(Failure($()), "NOK")
)
);
System.out.println(
Match(fail).of(
Case(Success($()), "OK"),
Case(Failure($(instanceOf(IllegalArgumentException.class))), "NOK - IAE"),
Case(Failure($()), "NOK")
)
);
}
Dopasowania krotek
Czyli mięsko, o które mnie pytano. Skoro można tworzyć wzorce dla takiego Try, to co stanie się, gdy zaczniemy używać wzorców dla Tuple?
Listing 3. Dopasowanie krotki
public void tuplePattern() {
Tuple3<String, String, String> of = Tuple.of("Ala", "nie ma", "kota");
System.out.println(
Match(of).of(
Case(Tuple3($(), $("ma"), $()), "Wiedźma!"),
Case(Tuple3($(), $("nie ma"), $()), "Pożeraczka kotów!")
)
);
}
W powyższym przykładzie mamy dodatkowo ukryte dopasowanie typów. Wszystkie wzorce muszą być typu Pattern.Tuple3. Technicznie wzorzec jest funkcją, która zwraca typ zgodny z typem obiektu dopasowywanego. Dlatego nie może być to zwykłe Tuple3. Jednocześnie nie możemy w żaden sposób pracować z krotkami o różnej liczności. Oczywiście rozwiązaniem jest użycie typu Tuple dla zmiennej of.
Listing 4. Dopasowanie dowolnej krotki
public void tuplePattern() {
Tuple3<String, String, String> of = Tuple.of("Ala", "nie ma", "kota");
System.out.println(
Match(of).of(
Case(Tuple3($(), $("ma"), $()), "Wiedźma!"),
Case(Tuple4($(), $("nie"), $("ma"), $()), "Pożeraczka kotów!!!"),
Case(Tuple3($(), $("nie ma"), $()), "Pożeraczka kotów!")
)
);
}
Oczywiście przekazanie krotki, która do niczego nie pasuje spowoduje wyjątek.
Podsumowanie
Mechanizm dopasowania wzorców jest bardzo elastyczny i umożliwia nam zupełnie inne podejście do tworzenia kodu. Oznacza to, że lepiej możemy dzielić logikę i dane. W połączeniu z innymi elementami mamy naprawdę silne narzędzie.