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.