Powiedzmy, że mamy normalną czystą funkcję, taką jak
function add(a, b) {
return a + b
}
A następnie zmieniamy go tak, aby miał efekt uboczny
function add(a, b) {
writeToDatabase(Math.random())
return a + b;
}
O ile wiem, nie jest to uważane za funkcję czystą, ponieważ często słyszę, jak ludzie nazywają funkcje czyste „funkcjami bez skutków ubocznych”. Zachowuje się jednak jak czysta funkcja, o ile zwraca te same dane wyjściowe dla tych samych danych wejściowych.
Czy istnieje inna nazwa dla tego typu funkcji, jest nienazwana, czy nadal jest rzeczywiście czysta i mylę się co do definicji czystości?
writeToDatabase
nie powiedzie, może wywołać wyjątek, powodując, że twoja drugaadd
funkcja generuje wyjątek, nawet jeśli wywoływana jest z tymi samymi argumentami, które wcześniej nie miały problemów ... przez większość czasu skutki uboczne wprowadzają tego rodzaju warunki związane z błędami, które się psują „czystość wejścia / wyjścia”.F(x)
zdefiniowano, abytrue
zwracał, jeśli jest wywoływany z tym samym argumentem, co poprzednie wywołanie. Oczywiście w sekwencji{1,2,2} => {undefined, false, true}
jest to deterministyczne, ale daje różne wynikiF(2)
.Odpowiedzi:
Nie jestem pewien co do uniwersalnych definicji czystości, ale z punktu widzenia Haskell (języka, w którym programiści dbają o takie rzeczy, jak czystość i przejrzystość referencyjna), tylko pierwsza z twoich funkcji jest „czysta”. Druga wersja
add
nie jest czysta . W odpowiedzi na twoje pytanie nazwałbym to „nieczystym”;)Zgodnie z tą definicją funkcja czysta jest funkcją, która:
Przy tej definicji jasne jest, że drugiej funkcji nie można uznać za czystą, ponieważ łamie ona zasadę 2. Oznacza to, że następujące dwa programy NIE są równoważne:
i
Wynika to z faktu, że chociaż obie funkcje zwrócą tę samą wartość, funkcja
f
zapisze dwa razy w bazie danych, aleg
zapisze raz! Jest bardzo prawdopodobne, że zapisy do bazy danych są częścią obserwowalnego zachowania twojego programu, w którym to przypadku pokazałem, że twoja druga wersjaadd
nie jest „czysta”.Jeśli zapisy do bazy danych nie są obserwowalną częścią zachowania twojego programu, wówczas obie wersje
add
można uznać za równoważne i czyste. Ale nie mogę wymyślić scenariusza, w którym zapis do bazy danych nie ma znaczenia. Nawet logowanie się ma znaczenie!źródło
f(x)
zależy nie tylko odx
, ale także od zewnętrznej zmiennej globalnejy
. Następnie, jeślif
ma właściwość RT, możesz dowolnie zamieniać wszystkie jej wystąpienia wartością zwracaną, dopóki nie dotknieszy
. Tak, mój przykład jest wątpliwy. Ale ważne jest to: jeślif
zapisuje do bazy danych (lub zapisuje w dzienniku), traci właściwość RT: teraz nie ma znaczenia, czy pozostawisz globalnyy
nietknięty, wiesz, jakie znaczenie ma twój program, w zależności od tego, czy faktycznie zadzwońf
lub po prostu użyj jego wartości zwracanej.Taka funkcja jest nazywana
W odniesieniu do stanu:
W zależności od tego, z której definicji funkcji korzystasz, funkcja nie ma stanu. Jeśli pochodzisz ze świata obiektowego, pamiętaj, że
x.f(y)
jest to metoda. W funkcji mogłoby to wyglądaćf(x,y)
. A jeśli jesteś w zamknięciach z zamkniętym zakresem leksykalnym, pamiętaj, że stan niezmienny może równie dobrze być częścią wyrażenia funkcji. Jest to tylko stan zmienny, który miałby wpływ na charakter deterministyczny funkcji. Zatem f (x) = x + 1 jest deterministyczne, o ile 1 się nie zmienia. Nie ma znaczenia, gdzie 1 jest przechowywany.Twoje funkcje są deterministyczne. Twoja pierwsza jest również czystą funkcją. Twoja sekunda nie jest czysta.
Punkt 1 oznacza deterministyczny . Punkt 2 oznacza przejrzystość referencyjną . Razem oznaczają, że czysta funkcja pozwala tylko na zmianę argumentów i zwracanej wartości. Nic innego nie powoduje zmiany. Nic innego się nie zmienia.
źródło
Math.random()
. Więc nie, chyba że założymy PRNG (zamiast fizycznego RNG) ORAZ nie weźmiemy pod uwagę, że PRNG stanowią część danych wejściowych (co nie jest, odniesienie jest zakodowane na stałe), nie jest deterministyczne.Jeśli nie obchodzi Cię efekt uboczny, jest on względnie przezroczysty. Oczywiście możliwe jest, że Cię to nie obchodzi, ale robi to ktoś inny, więc zastosowanie tego terminu zależy od kontekstu.
Nie znam ogólnego terminu określającego dokładnie opisywane właściwości, ale ważnym podzbiorem są te, które są idempotentne . W informatyce, nieco inaczej niż w matematyce *, funkcja idempotentna to taka, którą można powtórzyć z tym samym skutkiem; to znaczy, że efekt uboczny nett robienia tego wiele razy jest taki sam jak robienie tego raz.
Tak więc, jeśli efektem ubocznym było zaktualizowanie bazy danych o określonej wartości w określonym wierszu lub utworzenie pliku o dokładnie spójnej zawartości, byłoby to idempotentne , ale gdyby zostało dodane do bazy danych lub dołączone do pliku , to nie byłoby.
Kombinacje idempotentnych funkcji mogą, ale nie muszą, być idempotentnymi jako całością.
* Wydaje się, że użycie idempotenta w informatyce inaczej niż w matematyce wynika z niewłaściwego użycia matematycznego terminu, który został następnie przyjęty, ponieważ ta koncepcja jest przydatna.
źródło
(f x, f x)
zelet y = f x in (y, y)
będzie napotkasz z Disk Space-wyjątkami dwukrotnie szybciej można argumentować, że są to na marginesie przypadków, na których ci nie zależy, ale przy tak niewyraźnej definicji równie dobrze moglibyśmy nazwaćnew Random().Next()
referencyjnie przezroczystymi, bo do cholery, nie dbam o to, jaki numer i tak otrzymam.Random.Next
w .NET rzeczywiście ma skutki uboczne. Bardzo tak. Jeśli możeszNext
, przypisz ją do zmiennej, a następnie wywołajNext
ponownie i przypisz do innej zmiennej, prawdopodobnie nie będą one równe. Dlaczego? Ponieważ wywołanieNext
zmienia ukryty stan wewnętrzny wRandom
obiekcie. Jest to biegunowe przeciwieństwo przezroczystości referencyjnej. Nie rozumiem twojego twierdzenia, że „główne efekty” nie mogą być skutkami ubocznymi. W kodzie imperatywnym bardziej powszechne jest to, że głównym efektem jest efekt uboczny, ponieważ programy imperatywne są z natury stanowe.Nie wiem, jak wywoływane są takie funkcje (ani czy istnieje jakaś systematyczna nazwa), ale nazwałbym funkcję, która nie jest czysta (jak w przypadku innych odpowiedzi), ale zawsze zwraca ten sam wynik, jeśli jest dostarczana z tymi samymi parametrami parametry ”(w porównaniu do funkcji jego parametrów i niektórych innych stanów). Nazwałbym to po prostu funkcją, ale niestety, kiedy mówimy „funkcja” w kontekście programowania, mamy na myśli coś, co wcale nie musi być rzeczywistą funkcją.
źródło
Zasadniczo zależy to od tego, czy zależy ci na nieczystości. Jeśli semantyką tej tabeli jest to, że nie obchodzi Cię, ile jest wpisów, to jest czysta. W przeciwnym razie to nie jest czyste.
Innymi słowy, jest w porządku, o ile optymalizacje oparte na czystości nie naruszają semantyki programu.
Bardziej realistycznym przykładem może być próba debugowania tej funkcji i dodania instrukcji rejestrowania. Technicznie rejestrowanie jest efektem ubocznym. Czy dzienniki czynią to nieczystym? Nie.
źródło
Powiedziałbym, że najlepszą rzeczą, o którą należy zapytać, nie jest to, jak to nazwiemy, ale jak przeanalizujemy taki fragment kodu. A moim pierwszym kluczowym pytaniem w takiej analizie byłoby:
Można to łatwo zilustrować w Haskell (a zdanie to tylko pół żartu). Przykładem przypadku „nie” może być coś takiego:
W tym przykładzie działanie, które podejmujemy (drukujemy linię
"I'm doubling some number"
) nie ma wpływu na związek międzyx
i wynikiem. Oznacza to, że możemy to zmienić w ten sposób (używającApplicative
klasy i jej*>
operatora), co pokazuje, że funkcja i efekt są w rzeczywistości ortogonalne:Tak więc w tym przypadku osobiście powiedziałbym, że jest to przypadek, w którym można wyróżnić czystą funkcję. Dużo o tym mówi programowanie Haskell - uczenie się, jak oddzielić czyste części od efektywnego kodu.
Przykład rodzaju „tak”, w którym czyste i skuteczne części nie są ortogonalne:
Teraz ciąg, który drukujesz, zależy od wartości
x
. Część funkcji (pomnóżx
przez dwa) nie zależy jednak w ogóle od efektu, więc nadal możemy go rozdzielić:Mógłbym dalej wymieniać inne przykłady, ale mam nadzieję, że to wystarczy, aby zilustrować punkt, od którego zacząłem: nie „nazywasz” tego czymś, analizujesz, w jaki sposób łączą się czyste i skuteczne części, i rozróżniasz je, kiedy jest na twoją korzyść.
Jest to jeden z powodów, dla których Haskell
Monad
tak często wykorzystuje swoją klasę. Monady są (między innymi) narzędziem do przeprowadzania tego rodzaju analiz i refaktoryzacji.źródło
Funkcje, które są przeznaczone do wywoływania skutków ubocznych są często nazywane effectful . Przykład https://slpopejoy.github.io/posts/Effectful01.html
źródło