Lenistwo ponad wszystko
Lenistwo to cnota programisty. Tylko leniwy programista zrobi daną rzecz porządnie za pierwszym razem, ponieważ jako istota leniwa nie będzie chciał wracać do nudnego zadania, a skupić się na czymś ciekawszym.
Javaslang udostępnia nam klasę Lazy, która opakowuje leniwie wyznaczaną wartość.
Motywacja
Podobnie jak w przypadku memoryzacji funkcji chcemy zapamiętać wynik ciężkich obliczeń, by nie musieć ich powtarzać, tak w przypadku leniwej ewaluacji idziemy krok dalej i uruchamiamy obliczenia tylko wtedy gdy naprawdę musimy. Kiedyś już o tym opowiadałem, ale dziś szybko o tym, jak robi to Javaslang.
Kontener Lazy
Pierwszym sposobem w jaki możemy pracować z wartościami leniwymi jest bezpośrednie użycie kontenera Lazy:
Listing 1. Bezpośrednie użycie Lazy
public void container() {
Lazy<Integer> of = Lazy.of(() -> new Random().nextInt());
System.out.println(of.get());
System.out.println(of.get());
}
W tym przypadku pierwsze wywołanie of.get spowoduje, że zostanie wywołana metoda get z Suppliera, wyliczona i zapamiętana wartość, a wynik zwrócony. Kolejne wywołania będą już zwracać zapamiętaną wartość. Takie podejście jest bardzo proste i intuicyjne. Wymaga jednak od nas dodatkowego, jawnego, kroku w postaci wywołania get. Kolejna wada to zmiana typu. Wyobraźmy sobie, że chcemy mieć leniwie wyznaczony obiekt typu T, a mamy tylko Supplier\[T\]. Wywołanie get na Supplierze jest bez sensu, bo tracimy leniwość. Podobnie będzie w przypadku Lazy. Główną różnicą pomiędzy Lazy w tym wydaniu, a Supplierem jest memoryzacja, ale…
Prawdziwie leniwe obiekty
Lazy dostarcza też mechanizm tworzenia obiektów, o których możemy powiedzieć, iż są prawdziwie leniwe. W tym sensie prawdziwie, że mają swój własny typ, a jednocześnie są wyznaczane leniwie.
Listing 2. Prawdziwie leniwy obiekt
public void trueLazy() {
SomeInterface of = Lazy.val(() -> {
SomeInterface s = () -> new Random().nextInt();
return s;
}, SomeInterface.class);
System.out.println(of);
System.out.println(of);
}
Podobnie jak w poprzednim przypadku of będzie niezmienne w czasie. Najbardziej widoczna różnica to typ. W przypadku Lazy.val typ zwracanego obiektu jest taki, jakiego dostarcza Supplier. Pytanie, jak? Metoda val zwraca tak naprawdę Proxy, które posiada odpowiedni InvocationHandler, który wywołuje Lazy.get, a ten jak już wiemy, dostarcza odpowiednią wartość.
Podsumowanie
Używanie leniwych wartości jest bardzo dobrym pomysłem. Szczególnie jeżeli wiemy, że ich wyliczenie jest czasochłonne, a nie zawsze będą użyte. Javaslang dostarcza mechanizmów, które pozwalają na łatwe i proste tworzenie tego rodzaju rozwiązań.