Dwa warunki definiujące funkcję pure
są następujące:
- Brak skutków ubocznych (tj. Dozwolone są tylko zmiany w zakresie lokalnym)
- Zawsze zwracaj to samo wyjście, mając te same dane wejściowe
Jeśli pierwszy warunek jest zawsze prawdziwy, czy zdarza się, że drugi warunek nie jest prawdziwy?
Czy naprawdę jest to konieczne tylko w przypadku pierwszego warunku?
Odpowiedzi:
Oto kilka kontrprzykładów, które nie zmieniają zakresu zewnętrznego, ale nadal są uważane za nieczyste:
function a() { return Date.now(); }
function b() { return window.globalMutableVar; }
function c() { return document.getElementById("myInput").value; }
function d() { return Math.random(); }
(co wprawdzie zmienia PRNG, ale nie jest uważane za obserwowalne)Dostęp do zmiennych niestałych i innych niż lokalne jest wystarczający, aby móc naruszyć drugi warunek.
Zawsze myślę o dwóch warunkach czystości jako uzupełniających się:
Termin efekt uboczny odnosi się tylko do pierwszej, funkcji modyfikującej stan nielokalny. Jednak czasami operacje odczytu są również uważane za skutki uboczne: gdy są operacjami i obejmują również zapis, nawet jeśli ich głównym celem jest uzyskanie dostępu do wartości. Przykładami tego są generowanie liczby pseudolosowej, która modyfikuje stan wewnętrzny generatora, odczytywanie ze strumienia wejściowego, który przesuwa do przodu pozycję odczytu lub odczytywanie z czujnika zewnętrznego, który zawiera polecenie „wykonaj pomiar”.
źródło
prompt("you choose")
nie ma skutków ubocznych, powinniśmy cofnąć się o krok i wyjaśnić znaczenie skutków ubocznych.„Normalnym” sposobem wyrażenia tego, czym jest czysta funkcja , jest referencyjna przezroczystość . Funkcja jest czysta, jeśli jest referencyjnie przezroczysta .
Z grubsza mówiąc, przezroczystość referencyjna oznacza, że w dowolnym momencie programu można zastąpić wywołanie funkcji jej wartością zwracaną lub odwrotnie, bez zmiany znaczenia programu.
Na przykład, jeśli C
printf
byłyby referencyjnie przezroczyste, te dwa programy powinny mieć to samo znaczenie:printf("Hello");
i
5;
a wszystkie poniższe programy powinny mieć to samo znaczenie:
5 + 5; printf("Hello") + 5; printf("Hello") + printf("Hello");
Ponieważ
printf
zwraca liczbę zapisanych znaków, w tym przypadku 5.W przypadku funkcji staje się to jeszcze bardziej oczywiste
void
. Jeśli mam funkcjęvoid foo
, topowinien być taki sam jak
To znaczy, ponieważ
foo
nic nie zwraca, powinienem być w stanie zastąpić to niczym bez zmiany znaczenia programu.Jest więc jasne, że ani, ani
printf
niefoo
są referencyjnie przezroczyste, a zatem żadne z nich nie jest czyste. W rzeczywistościvoid
funkcja nigdy nie może być referencyjnie przezroczysta, chyba że nie jest operacją.Uważam, że ta definicja jest znacznie łatwiejsza w obsłudze niż ta, którą podałeś. Pozwala również na zastosowanie go w dowolnej szczegółowości: możesz zastosować go do pojedynczych wyrażeń, do funkcji, do całych programów. Pozwala na przykład porozmawiać o takiej funkcji:
func fib(n): return memo[n] if memo.has_key?(n) return 1 if n <= 1 return memo[n] = fib(n-1) + fib(n-2)
Możemy przeanalizować wyrażenia, które składają się na funkcję i łatwo stwierdzić, że nie są one referencyjnie przezroczyste, a zatem nie są czyste, ponieważ używają zmiennej struktury danych, a mianowicie
memo
tablicy. Możemy jednak również spojrzeć na funkcję i zobaczyć, że jest ona referencyjnie przezroczysta, a zatem czysta. Nazywa się to czasem czystością zewnętrzną , tj. Funkcją, która wydaje się czysta dla świata zewnętrznego, ale wewnętrznie jest zaimplementowana jako nieczysta.Takie funkcje są nadal przydatne, ponieważ podczas gdy zanieczyszczenie infekuje wszystko wokół siebie, zewnętrzny czysty interfejs tworzy rodzaj „bariery czystości”, w której zanieczyszczenie infekuje tylko trzy wiersze funkcji, ale nie przedostaje się do reszty programu . Te trzy wiersze są znacznie łatwiejsze do przeanalizowania pod kątem poprawności niż cały program.
źródło
memo[n]
jest idempotentne, a niepowodzenie odczytu z niego powoduje jedynie marnowanie cykli procesora.memo[n] = ...
może najpierw utworzyć wpis w słowniku, a następnie zapisać w nim wartość. To pozostawia okno, w którym inny wątek może zobaczyć niezainicjowany wpis.Wydaje mi się, że drugi warunek, który opisałeś, jest słabszym ograniczeniem niż pierwszy.
Podam ci przykład, przypuśćmy, że masz funkcję dodawania takiej, która również loguje się do konsoli:
function addOneAndLog(x) { console.log(x); return x + 1; }
Drugi podany warunek jest spełniony: ta funkcja zawsze zwraca te same dane wyjściowe, gdy ma te same dane wejściowe. Nie jest to jednak czysta funkcja, ponieważ zawiera efekt uboczny logowania do konsoli.
Czysta funkcja to, ściśle mówiąc, funkcja spełniająca właściwość funkcji przezroczystości referencyjnej . Jest to właściwość, którą możemy zastąpić aplikację funkcji wartością, którą wytwarza, bez zmiany zachowania programu.
Załóżmy, że mamy funkcję, która po prostu dodaje:
function addOne(x) { return x + 1; }
Możemy wymienić
addOne(5)
w6
dowolnym miejscu w naszym programie i nic się nie zmieni.Natomiast nie możemy tego zastąpić
addOneAndLog(x)
wartością6
w naszym programie bez zmiany zachowania, ponieważ pierwsze wyrażenie powoduje, że coś jest zapisywane na konsoli, a drugie nie.Każde z tych dodatkowych zachowań, które
addOneAndLog(x)
występuje poza zwracaniem danych wyjściowych, traktujemy jako efekt uboczny .źródło
Date.now()
nie jest czyste / referencyjnie przezroczyste, ale nie dlatego, że ma skutki uboczne, ale dlatego, że jego wynik zależy od czegoś więcej niż tylko jego wkładu.Może istnieć źródło losowości spoza systemu. Załóżmy, że część obliczeń obejmuje temperaturę w pomieszczeniu. Wtedy wykonanie funkcji za każdym razem da różne wyniki w zależności od losowego elementu zewnętrznego temperatury pokojowej. Stan nie jest zmieniany przez wykonanie programu.
W każdym razie wszystko, o czym mogę myśleć.
źródło
Problem z definicjami PR polega na tym, że są one bardzo sztuczne. Każda ocena / obliczenie ma skutki uboczne dla oceniającego. To teoretycznie prawda. Zaprzeczanie temu pokazuje jedynie, że apologeci FP ignorują filozofię i logikę: „ocena” oznacza zmianę stanu jakiegoś inteligentnego środowiska (maszyny, mózgu itp.). Taka jest natura procesu oceny. Bez zmian - bez „kamieni”. Efekt może być bardzo widoczny: rozgrzanie procesora lub jego awaria, wyłączenie płyty głównej w przypadku przegrzania i tak dalej.
Kiedy mówisz o przezroczystości referencyjnej, powinieneś zrozumieć, że informacje o takiej przezroczystości są dostępne dla człowieka jako twórcy całego systemu i posiadacza informacji semantycznej i mogą być niedostępne dla kompilatora. Na przykład funkcja może odczytać jakiś zasób zewnętrzny i będzie miała monadę IO w podpisie, ale będzie zwracać tę samą wartość przez cały czas (na przykład wynik
current_year > 0
). Kompilator nie wie, że funkcja zwróci zawsze ten sam wynik, więc funkcja jest nieczysta, ale ma referencyjną przezroczystość i można ją zastąpićTrue
stałą.Aby więc uniknąć takiej niedokładności, powinniśmy rozróżnić funkcje matematyczne i „funkcje” w językach programowania. Funkcje w Haskell są zawsze nieczyste, a związana z nimi definicja czystości jest zawsze bardzo warunkowa: działają one na prawdziwym sprzęcie z rzeczywistymi efektami ubocznymi i właściwościami fizycznymi, co jest złe w przypadku funkcji matematycznych. Oznacza to, że przykład z funkcją „printf” jest całkowicie niepoprawny.
Ale nie wszystkie funkcje matematyczne są również czyste: każda funkcja, która ma
t
(czas) jako parametr, może być nieczysta:t
zawiera wszystkie efekty i stochastyczny charakter funkcji: w typowym przypadku masz sygnał wejściowy i nie masz pojęcia o rzeczywistych wartościach, może być nawet hałasem.źródło
tak
Rozważ prosty fragment kodu poniżej
public int Sum(int a, int b) { Random rnd = new Random(); return rnd.Next(1, 10); }
Ten kod zwróci losowe dane wyjściowe dla tego samego podanego zestawu danych wejściowych - jednak nie ma to żadnego efektu ubocznego.
Ogólny efekt obu punktów # 1 i # 2, o których wspomniałeś, w połączeniu razem oznacza: W dowolnym momencie, jeśli funkcja
Sum
z tym samym i / p zostanie zastąpiona wynikiem w programie, ogólne znaczenie programu nie zmienia się . To nic innego jak przejrzystość referencyjna .źródło
rnd
nie wymyka się funkcji, więc fakt, że zmienia się jej stan, nie ma znaczenia dla czystości funkcji, ale fakt, żeRandom
konstruktor używa bieżącego czasu jako wartości początkowej, oznacza, że istnieją „wejścia” inne niża
ib
.