Powiedzmy, że fn(x)
jest to czysta funkcja, która robi coś kosztownego, na przykład zwraca listę głównych czynników x
.
Powiedzmy, że tworzymy zapamiętaną wersję tej samej funkcji memoizedFn(x)
. Zawsze zwraca ten sam wynik dla danych wejściowych, ale zachowuje prywatny bufor poprzednich wyników w celu poprawy wydajności.
Formalnie rzecz biorąc, jest memoizedFn(x)
uważany za czysty?
Czy może jest jakaś inna nazwa lub termin kwalifikacyjny używany w odniesieniu do takiej funkcji w dyskusjach dotyczących PR? (tj. funkcja z efektami ubocznymi, które mogą wpływać na złożoność obliczeniową kolejnych wywołań, ale mogą nie wpływać na zwracane wartości).
terminology
pure-function
kalus
źródło
źródło
funcx(){sleep(cached_time--); return 0;}
zwraca tę samą wartość za każdym razem, ale będzie działać inaczejOdpowiedzi:
Tak. Zapamiętana wersja funkcji czystej jest również funkcją czystą.
Wszystko, na czym zależy dbałość o czystość funkcji, to wpływ, jaki parametry wejściowe na wartość zwracaną przez funkcję (przekazanie tego samego wejścia powinno zawsze dawać to samo wyjście) i wszelkie skutki uboczne istotne dla stanów globalnych (np. Tekst na terminal, interfejs użytkownika lub sieć) . Czas obliczeń i dodatkowe użycie pamięci nie mają znaczenia dla czystości funkcji.
Pamięci podręczne czystej funkcji są prawie niewidoczne dla programu; funkcjonalny język programowania może automatycznie optymalizować czystą funkcję do zapamiętywanej wersji funkcji, jeśli może stwierdzić, że będzie to korzystne. W praktyce automatyczne określenie, kiedy zapamiętywanie jest korzystne, jest w rzeczywistości dość trudnym problemem, ale taka optymalizacja byłaby ważna.
źródło
Wikipedia definiuje „czystą funkcję” jako funkcję, która ma następujące właściwości:
Zwracana wartość jest taka sama dla tych samych argumentów (bez zmian z lokalnymi zmiennymi statycznymi, zmiennymi nielokalnymi, zmiennymi argumentami referencyjnymi lub strumieniami wejściowymi z urządzeń I / O).
Jego ocena nie ma skutków ubocznych (brak mutacji lokalnych zmiennych statycznych, zmiennych nielokalnych, zmiennych argumentów referencyjnych lub strumieni We / Wy).
W efekcie czysta funkcja zwraca to samo wyjście przy tych samych danych wejściowych i nie wpływa na nic poza funkcją. Dla celów czystości nie ma znaczenia, w jaki sposób funkcja oblicza wartość zwracaną, o ile zwraca to samo wyjście przy tych samych danych wejściowych.
Funkcjonalnie czyste języki, takie jak Haskell, rutynowo używają zapamiętywania, aby przyspieszyć funkcję poprzez buforowanie wcześniej obliczonych wyników.
źródło
static const
zmienna lokalna w C ++ (ale nie C) lub leniwie oceniana struktura danych w Haskell. Jest jeszcze jeden warunek, którego potrzebujesz: inicjalizacja musi być bezpieczna dla wątków.Tak, zapamiętane funkcje czyste są powszechnie nazywane czystymi. Jest to szczególnie powszechne w językach takich jak Haskell, w których zapamiętywane, leniwie oceniane, niezmienne wyniki są wbudowaną funkcją.
Jest jedno ważne zastrzeżenie: funkcja zapamiętywania musi być bezpieczna dla wątków, w przeciwnym razie możesz uzyskać warunki wyścigu, gdy dwa wątki będą próbowały to nazwać.
Jednym z przykładów informatyków używających w ten sposób terminu „czysto funkcjonalny” jest post na blogu Conal Elliott na temat automatycznej zapamiętywania:
Istnieje wiele przykładów w recenzowanej literaturze, i były one od dziesięcioleci. Na przykład w artykule z 1995 r. „Używanie automatycznej zapamiętywania jako narzędzia inżynierii oprogramowania w rzeczywistych systemach AI”, w bardzo podobnym języku w rozdziale 5.2 opisano, co dziś nazwalibyśmy czystą funkcją:
Niektóre imperatywne języki mają podobny idiom. Na przykład
static const
zmienna w C ++ jest inicjowana tylko raz, przed użyciem jej wartości i nigdy się nie mutuje.źródło
To zależy od tego, jak to robisz.
Zwykle ludzie chcą zapamiętywać, mutując jakiś słownik pamięci podręcznej. Ma to wszystkie problemy związane z mutacją nieczystą, takie jak martwienie się o współbieżność, martwienie się o zbyt duży rozmiar pamięci podręcznej itp.
Możesz jednak robić notatki bez mutacji nieczystej pamięci. Jednym z przykładów jest ta odpowiedź , w której śledzę zapamiętane wartości zewnętrznie za pomocą
lengths
argumentu.W linku podanym przez Roberta Harveya zastosowano leniwą ocenę, aby uniknąć skutków ubocznych.
Inną techniką, którą czasem można zobaczyć, jest wyraźne oznaczenie zapamiętywania jako nieczystego efektu ubocznego w kontekście
IO
typu, na przykład z funkcją zapamiętywania efektu kota .Ten ostatni wskazuje na to, że czasem celem jest po prostu kapsułkowanie mutacji, a nie jej eliminowanie. Większość funkcjonalnych programistów uważa, że jest „wystarczająco czysty”, aby zanieczyszczenie było wyraźne i zamknięte.
Jeśli chcesz, aby termin odróżniał go od naprawdę czystej funkcji, myślę, że wystarczy powiedzieć „zapamiętany ze zmiennym słownikiem”. Dzięki temu ludzie wiedzą, jak bezpiecznie z niego korzystać.
źródło
collatz(100)
icollatz(200)
. I IIUIC, problem ze zbyt dużym wzrostem pamięci podręcznej (choć Haskell może mieć w tym kilka fajnych sztuczek?).IO
jest czysty. Wszystkie nieczyste metodyIO
i Koty są nazywaneunsafe
.Async.memoize
jest również czysty, więc nie musimy zadowolić się „czystością” :)Zwykle funkcja, która zwraca listę, nie jest wcale czysta, ponieważ wymaga alokacji pamięci i może przez to zawieść (np. Przez zgłoszenie wyjątku, który nie jest czysty). Język, który ma typy wartości i może reprezentować listę jako typ wartości o ograniczonym rozmiarze, może nie mieć tego problemu. Z tego powodu twój przykład prawdopodobnie nie jest czysty.
Zasadniczo, jeśli zapamiętywanie można wykonać w sposób wolny od błędów (np. Poprzez statycznie przydzielone miejsce na zapamiętane wyniki i wewnętrzną synchronizację w celu kontroli dostępu do nich, jeśli język dopuszcza wątki), uzasadnione jest rozważenie takiej funkcji czysty.
źródło
Za pomocą monady stanu możesz zaimplementować zapamiętywanie bez skutków ubocznych .
W twoim przypadku stanem będzie zapamiętana wartość lub nic (tj. Haskell
Maybe
lub ScalaOption[A]
). Jeśli zapamiętana wartość jest obecna, jest zwracana jakoA
, w przeciwnym razieA
jest obliczana i zwracana zarówno jako stan przejściowy, jak i wynik.źródło