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ń.