Funkcja niezmieniająca i konsumenci w Guavie
W poprzednim wpisie poświęconym Guavie wspomniałem, że przyjrzymy się sprawie wywołania loggera w naszym if-potworku.
Listing 1. If-potworek
for (ReportRunHistory reportRunHistory : reportRunsHistory) {
LOG.info(reportRunHistory.toString());
if (reportRunHistory.status.equals(CANCELED.getStatusText())
|| reportRunHistory.status.equals(FAILED.getStatusText())
|| reportRunHistory.status.equals(COMPLETED.getStatusText())){
// ...
}
}
Sprawę ifów mamy już rozwiązaną. Czas przyjrzeć się temu co można zrobić z pierwszą linijką. Tak naprawdę to co tu potrzebujemy to monadaW. Taka zwykła, smutna, haskellowa monada. Na monadę składają się dwie operacje (ok to uproszczenie, ale tu wystarczy) wiązanie i powrót… ok nie będzie o monadach, bo takie rozwiązanie choć możliwe do zaimplementowania zapewne zniszczy nam mózgi. My naprawdę żyjemy w świecie obiektowym więc i rozwiązanie będzie obiektowe. Co prawda mamy sobie coś co się nazywa Option i jest to taka dość prymitywna w użyciu monada, ale my chcemy mieć rozwiązanie problemu, a nie kolejny problem zastępczy (w razie jak by pierwszego nie było).
Zapomnijmy więc o monadach.
Konsument konsumuje
Tyle tylko, że w Guavie nie ma interfejsu Consumer<T>. samodzielne dopisanie go nie stanowi problemu
Listing 2. interfejs Consumer<T>
public interface Consumer<T> {
public void consume(T t);
}
Trochę większym problemem jest jego zastosowanie. Jeżeli na ten przykład zamiast takiej implementacji zastosowalibyśmy coś co rozszerza istniejącą funkcjonalność w rodzaju
Listing 3. interfejs Consumer<T> jako rozszerzenie funkcji
public interface Consumer<T> extends Function<T, Void>{
}
I następnie użyli znanej nam metody compose to byśmy mieli poważny problem. Metoda ta jak pamiętamy przyjmuje dwie funkcje i zwraca trzecią spełniającą zależność
// offtopic
obrazek wygenerowany za pomocą apki EqGraph. Czyli kolejnego wielkiego niedokończonego projektu 😉
// koniec offtopicu
W naszym przypadku oznaczało by to funkcję zwracającą Void, co było by równoznaczne z operacją kończącą. Nie za dobrze. Dlatego też można to zrealizować w trochę koślawy sposób:
Listing 4. Funkcja przyjmująca interfejs Consumer<T>
public class InConsumerFunction<I> implements Function<I, I> {
private final Consumer<I> consumer;
public InConsumerFunction(Consumer<I> consumer) {
this.consumer = consumer;
}
@Override
public I apply(I input) {
consumer.consume(input);
return input;
}
}
Nie jest to najlepsze możliwe rozwiązanie, ale jak na nasze potrzeby na obecnym etapie wystarczające. Co tu się dzieje jest chyba jasne. Tworząc instancję naszej funkcji przekazujemy jej jakiegoś konsumenta, który robi swoje, a następnie funkcja zwraca oryginalny obiekt.
Dlaczego jest to koślawe?
Po pierwsze dlatego, że konsument może zmienić stan naszego obiektu. Programowanie funkcyjne opiera się o niezmienność poszczególnych bytów. Tu nie mamy możliwości zagwarantowania braku zmiany w konsumencie… bywa…
Po drugie sama funkcja posiada stan. To znacząco ogranicza możliwość jej powtórnego użycia. Z drugiej strony dobrze przemyślany system logowania powinien pozwolić na wywołania silnie typowane. Co to oznacza? Ano oznacza tyle, że treść komunikatów jest sobie zamknięta w jakiejś klasie i nie mamy dostępu do nich z zewnątrz. Jedyne co możemy zrobić to przekazać im listę argumentów.
Da się to nawet napisać i zapewne kolejny post będzie poświęcony tej tematyce, ale to nadal jest konieczność pałowania się z czymś na co nie mamy ochoty.
Po trzecie musieliśmy wprowadzić dodatkowy byt, którego użycie wprost z poziomu Guavy jest niemożliwe. Wymaga on każdorazowego opakowania. Nie za dobrze.
Podsumowanie
Guava daje nam bardzo dużo możliwości. Jednocześnie brakuje w niej „duperelków”, których dopisanie jest uciążliwe i wymaga stosowania haków o ile chcemy napisać coś w możliwie ogólny sposób. Jednocześnie pisanie rozwiązań dla konkretnych problemów może okazać się znacznie prostsze i efektywniejsze.
O tym jak dobrać rozwiązanie do problemu i czy opłaca się pisać rozwiązania uogólnione będę mówił w trakcie warsztatów “Beginning Functional Programming in Java world” na Warsjawie już 26-27 września.
Na koniec mała prośba jeżeli spodobał ci się ten wpis udostępnij go w mediach społecznościowych korzystając z przycisków poniżej. Jak masz pytania zadaj je w komentarzu. Postaram się zaspokoić twoją ciekawość.