Memoizacja FP - praktyczne zastosowanie

1

W językach z dobrym wsparciem FP (Clojure np. ) czy bibibliotekach do FP (Vavr) mamy możliwość stosowania memoizacji czyli cachowania jej wyniku. Tutaj pojawia się w sumie takie pytanie parafrazując klasyka "na co to komu? Po co to potrzebne?". Generalnie na ogół jeśli coś cachujemy to wyniki operacji na bazach danych, czy wynik z jakiegoś serwisu zewnętrznego(np. cache z zapytania HTTP), więc tutaj tego typu memoizacja się nie przydaje na ogół (nie ma na przyklad możliwośći ustawienia czasu życia cache). Pytanie jakie wobec tego się narzuca - to jakie jest praktyczne zastosowanie tego mechanizmu? No bo umówmy się, fibonacciego w kodzie biznesowym na ogół nie stosujemy :D
Wzywam tych co mogą wiedzieć
@jarekr000000
@Wibowit

1

Zależy co konkretnie rozumieć przez memoizację, ale w Scali dość często wykorzystuje się lazy val. Np:

lazy val heavy = ... // coś ciężkiego do policzenia
val resultA = if (condition1) heavy.field1 else quick(5)
if (condition2) heavy.field1 + resultA.field2 else resultA.field3

Zmienna heavy zostanie policzona tylko gdy jest potrzebna i przez to że jest zapamiętana to zostanie policzona maksymalnie jeden raz. Do tego lazy val niektórzy wykorzystują do automatycznego rozwiązywania kolejności inicjalizacji:

class Xxx {
  lazy val member1 = member2.something + somethingElse
  lazy val member2 = yetAnotherThing
}

Scala ma też Stream https://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html i LazyList https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html ale widuję je bardzo rzadko. Kolega użył tego do spamiętywania imperatywnych prób ponawiania pewnej akcji, by móc zarówno zatrzymać się po pierwszym sukcesie jak i wygodnie powrócić do wszystkich wyników (zarówno pierwszego sukcesu jak i wszystkich poprzednich porażek).

0

@Wibowit: miałem na myśli zapamiętanie rezultatu funkcji dla danych argumentów wejściowych.

0

No to takiego czegoś nie widziałem. Zwykle po prostu robię jakąś mapę, zbiór, sekwencję, etc ale bez leniwie obliczanych udziwnień.

0
Wibowit napisał(a):

No to takiego czegoś nie widziałem. Zwykle po prostu robię jakąś mapę, zbiór, sekwencję, etc ale bez leniwie obliczanych udziwnień.

Z dokumentacji Vavr:

Function0<Double> hashCache = Function0.of(Math::random).memoized();

double randomValue1 = hashCache.apply();
double randomValue2 = hashCache.apply();

then(randomValue1).isEqualTo(randomValue2);
1
Wibowit napisał(a):

No to takiego czegoś nie widziałem. Zwykle po prostu robię jakąś mapę, zbiór, sekwencję, etc ale bez leniwie obliczanych udziwnień.

Nie trzeba się silić na leniwość, wystarczy użyć np. memoizującej monady.

1

Chodziło mi o to, że w biznesowym kodzie takich rzeczy nie widziałem. W tutorialach owszem, ale nie w komercyjnym kodzie. A OPowi chodziło o tzw. praktyczne zastosowania.

0

A nie jest to problem klasy "Moja implementacja stosu będzie prostsza i lepsza niż ta ze standardowej implementacji"? ;-)

0

Nie jest to automatyczna memoizacja, ale zawsze. W Erlangu (od OTP 21.2) moduł persistent_term, który działa jak globalny mutowalny KV. Jest on ultra-szybki jeśli chcemy czytać z niego wartości, bo de facto działa jak dynamicznie kompilowany moduł z listą funkcji. Wadą tego rozwiązania jest to, że pisanie do niego jest dość kosztowne, bo trzeba przejść przez przez wszystkie procesy i skopiować usuwaną ze współdzielonej pamięci do pamięci danego procesu. W związku z tym zastosowania są dość oczywiste - konfiguracja różnych komponentów, obecnie głównie loggera. Wartości nie są liczone tak długo jak nie są potrzebne by nie dodawać pracy procesom, które nic nie logują, ale jeśli trzeba to jest wszystko obliczane w locie i zapisywane. Zakładamy, że konfiguracja nie zmienia się za często, więc można pozwolić sobie na to by były one kosztowne w sytuacjach gdy jednak trzeba coś zmienić.

1 użytkowników online, w tym zalogowanych: 0, gości: 1