Na początek kilka spraw organizacyjnych. Jak widać pisanie po wiosennym ruchu idzie mi średnio 🙂 Ale się staram. Powodów jest kilka, jeden ma 5 lat, drugi 2 miesiące 🙂 Do tego dość dużo pracy i zdecydowanie za krótka doba.

26 listopada będę mówił o Elixirze na Infomeet. Jak chcecie posłuchać dlaczego moim zdaniem warto, to zapraszam. Slajdy z prezentacji, która odbyła się w ramach targów KarieraIT tutaj. I lecimy z tematem.

Either kto to?

Jednym z problemów związanych ze zwracaniem wyników z funkcji (w rozumieniu zarówno metody obiektu, jak i „właściwej” funkcji) jest „upchnięcie” informacji o wyniku i jego poprawności. Klasycznie można taki kod napisać w następujący sposób:

Listing 1. Klasyczne podejście do problemu

public int sumUpTo(int limit) throws SumException{
	//....
}

Mamy sobie metodę, która rzuca wyjątek, jeżeli coś się jej nie spodoba. Takie rozwiązanie jest OK, lecz ma kilka wad. Po pierwsze wyjątek należy przechwycić. O ile wyjątków niekontrolowanych nie musimy obsługiwać w jawny sposób, to jednak w porządnie napisanym sofcie należy pomyśleć o jakimś mechanizmie do ich przechwytywania. Niestety w klasycznym podejściu walimy blok catch(Throwable) gdzieś wysoko i nam to rybka. Po drugie wyjątki są stosunkowo ciężkim mechanizmem. Wyrzucenie wyjątku kontrolowanego wymaga dodatkowej pracy od JVMki, by odpowiednio posprzątać stan aplikacji bardziej szczegółowy opis tutaj. Po drugie czasami narzucone API ogranicza nasze możliwości, jeśli chodzi o wyjątki i musimy korzystać z konstrukcji przechwyć-opakuj-rzuć. Zazwyczaj zamieniamy wyjątek kontrolowany na ogólny RuntimeException. Po trzecie taka metoda, nawet jak rzuca wyjątek niekontrolowany, to bywa trudnym przypadkiem, gdy chcemy korzystać z kompozycji, czy strumieni. Po prostu napiszemy się tych map i filter, a potem i tak trzeba try-catch, bo coś.

Rozwiązaniem jest użycie Either z biblioteki Javaslang. Jest to interfejs, który posiada dwie implementacje Left i Right. Dodatkowo istnieje konwencja, że poprawny wynik trafia do Right, a błędny do Left. Nie jest ona w żaden sposób weryfikowana, ale API niejako „promuje” użycie Rigth, gdyż funkcje domyślnie korzystają z tej wartości. Można zatem przepisać naszą metodę do:

Listing 2. Zmiana API na korzystające z Either

public Either<String, Integer> sumUpTo(int limit){
	//....
}

Either po co to?

Możemy potraktować Either jako rodzaj kontenera na wynik operacji. Jeżeli zakończy się błędem, to zostanie wypełniona jego lewa strona, w przeciwnym wypadku wypełniona będzie prawa. Bazując na tej informacji, możemy wprowadzić funkcje takie jak map czy filter. Pozwolą one na budowę odpowiedniego przepływu w aplikacji. Przykładowo jeżeli, chcemy wynik naszej funkcji zamienić na komunikat:

Listing 3. Wykorzystanie Either.map

public static void main(String[] args) {
	sumUpTo(10).map(s -> s + "").forEach(System.out::println);
}

Jeżeli z jakiegoś powodu nasza funkcja zwróci błąd, to wynik będzie… pusty. Po prostu mapowanie się nie wykona. Tu uwidacznia się pewna cecha Either. Jest on „stronniczy”, to znaczy gdy z nim pracujemy musimy określić, która strona nas interesuje. Funkcja map pracuje z wartością Right, ale jest też mapLeft. W dodatku mamy jeszcze projekcje…

Projekcje

Projekcje, to nic innego jak opakowane wartości. Dostarczają nam one odpowiednich funkcji w rodzaju map, filter itd., ale już w kontekście odpowiedniej wartości. By się do nich dobrać, należy wywołać right albo left na obiekcie Either. Kod z listingu 3 można zapisać jako:

Listing 4. Wykorzystanie projekcji

public static void main(String[] args) {
	sumUpTo(10).right().map(s -> s + "").forEach(System.out::println);
}

Jawnie opowiadając się po jednej ze stron, możemy skupić się tylko na interesującej nas części wartości albo błędzie.

Podsumowanie

Oczywiście nie przedstawiłem tu wszystkich możliwości Either. Zachęcam do zapoznania się z dokumentacją, kodem oraz samodzielnego eksperymentowania.