Widziałem to w paradygmatach imperatywnych
f (x) + f (x)
może nie być taki sam jak:
2 * f (x)
Ale w paradygmacie funkcjonalnym powinno być tak samo. Próbowałem zaimplementować oba przypadki w Pythonie i Scheme , ale dla mnie wyglądają tak samo prosto.
Jaki byłby przykład, który mógłby wskazać różnicę w stosunku do danej funkcji?
f(x++)+f(x++)
może nie być tym samym, co2*f(x++)
(w C jest szczególnie piękny, gdy takie rzeczy są ukryte w makrach - czy złamałem sobie nos na tym? założycie się)f(x++)+f(x++)
może być absolutnie wszystkim, ponieważ wywołuje niezdefiniowane zachowanie. Ale to nie jest tak naprawdę związane z przejrzystością referencyjną - co nie pomogłoby w tym wywołaniu, jest „niezdefiniowane” dla referencyjnie przejrzystych funkcji, jaksin(x++)+sin(x++)
również w. Może mieć 42 lata, może sformatować dysk twardy, demony wylatują z nosa użytkownika…Odpowiedzi:
Przezroczystość referencyjna, odnosząca się do funkcji, wskazuje, że wynik zastosowania tej funkcji można określić tylko na podstawie wartości jej argumentów. Możesz pisać referencyjnie przezroczyste funkcje w dowolnym języku programowania, np. Python, Scheme, Pascal, C.
Z drugiej strony, w większości języków możesz także pisać funkcje nie referencyjnie przejrzyste. Na przykład ta funkcja Pythona:
nie jest referencyjnie przejrzysty, w rzeczywistości dzwoni
i
wygeneruje różne wartości dla każdego argumentu
x
. Powodem tego jest to, że funkcja używa i modyfikuje zmienną globalną, dlatego wynik każdego wywołania zależy od tego zmieniającego się stanu, a nie tylko od argumentu funkcji.Haskell, język czysto funkcjonalny, ściśle oddziela ocenę wyrażeń, w której stosowane są funkcje czyste i która zawsze jest referencyjnie przejrzysta, od wykonania akcji (przetwarzanie wartości specjalnych), która nie jest referencyjnie przejrzysta, tj. Wykonanie tej samej akcji może mieć za każdym razem inny wynik.
Tak więc dla dowolnej funkcji Haskell
i dowolną liczbą całkowitą
x
, zawsze tak jestPrzykładem akcji jest wynik funkcji biblioteki
getLine
:W wyniku oceny wyrażenia ta funkcja (właściwie stała) przede wszystkim generuje czystą wartość typu
IO String
. Wartości tego typu są wartościami jak każda inna: możesz je przekazywać, umieszczać w strukturach danych, komponować je za pomocą specjalnych funkcji i tak dalej. Na przykład możesz zrobić listę takich akcji:Działania są wyjątkowe, ponieważ można powiedzieć środowisku wykonawczemu Haskell, aby je wykonał, pisząc:
W takim przypadku, po uruchomieniu programu Haskell, środowisko wykonawcze przechodzi przez akcję związaną z nią
main
i wykonuje ją, prawdopodobnie powodując efekty uboczne. Dlatego wykonywanie akcji nie jest względnie przejrzyste, ponieważ dwukrotne wykonanie tej samej akcji może dać różne wyniki w zależności od tego, co środowisko wykonawcze otrzymuje jako dane wejściowe.Dzięki systemowi typów Haskell nigdy nie można użyć akcji w kontekście, w którym oczekiwany jest inny typ, i odwrotnie. Jeśli więc chcesz znaleźć długość łańcucha, możesz użyć
length
funkcji:zwróci 5. Ale jeśli chcesz znaleźć długość ciągu odczytywanego z terminala, nie możesz pisać
ponieważ pojawia się błąd typu:
length
oczekuje wprowadzenia listy typów (a String jest rzeczywiście listą), alegetLine
jest wartością typuIO String
(akcja). W ten sposób system typów zapewnia, że wartość akcji podobnagetLine
(której wykonanie odbywa się poza językiem podstawowym i która może być niereferencyjnie przezroczysta) nie może być ukryta wewnątrz wartości typu non-actionInt
.EDYTOWAĆ
Aby odpowiedzieć na pytanie exizt, oto mały program Haskell, który odczytuje wiersz z konsoli i drukuje jego długość.
Główna akcja składa się z dwóch podgrup, które są wykonywane sekwencyjnie:
getline
typuIO String
,putStrLn
typuString -> IO ()
na podstawie jej argumentu.Dokładniej, druga akcja jest zbudowana przez
line
z wartością odczytaną przez pierwszą akcję,length
(oblicz długość jako liczba całkowita), a następnieshow
(zamień liczbę całkowitą na ciąg znaków),putStrLn
do wynikushow
.W tym momencie można wykonać drugą akcję. Jeśli wpiszesz „Cześć”, wyświetli się „5”.
Zauważ, że jeśli otrzymujesz wartość z akcji za pomocą
<-
notacji, możesz użyć tej wartości tylko w innej akcji, np. Nie możesz napisać:ponieważ
show (length line)
ma typ,String
podczas gdy notacja wymaga, aby po akcji (getLine
typuIO String
) następowała kolejna akcja (np.putStrLn (show (length line))
typuIO ()
).EDYCJA 2
Definicja przejrzystości referencyjnej Jörga W. Mittaga jest bardziej ogólna niż moja (poparłem jego odpowiedź). Użyłem ograniczonej definicji, ponieważ przykład w pytaniu koncentruje się na wartości zwracanej funkcji i chciałem zilustrować ten aspekt. Jednak RT ogólnie odnosi się do znaczenia całego programu, w tym zmian stanu globalnego i interakcji ze środowiskiem (IO) spowodowanych oceną wyrażenia. Tak więc, aby uzyskać poprawną, ogólną definicję, należy odnieść się do tej odpowiedzi.
źródło
IO
typ Haskella dość łatwo w dowolnym języku za pomocą lambd i generycznych, ale ponieważ każdy może wywoływaćprintln
bezpośrednio, implementacjaIO
nie gwarantuje czystości; to byłaby tylko konwencja.getLine
braku przejrzystości referencyjnej jest błędne. PrezentujeszgetLine
tak, jakby oceniał lub redukował do jakiegoś ciągu, którego określony ciąg zależy od danych wejściowych użytkownika. To jest niepoprawne.IO String
nie zawiera ciągu znaków więcej niżMaybe String
.IO String
jest receptą na być może uzyskanie Sznurka, a co więcej, jest tak czysty, jak każdy inny w Haskell.Jednak nie to oznacza przejrzystość referencyjna. RT oznacza, że możesz zastąpić dowolne wyrażenie w programie wynikiem oceny tego wyrażenia (lub odwrotnie) bez zmiany znaczenia programu.
Weźmy na przykład następujący program:
Ten program jest względnie przejrzysty. Mogę wymienić jeden lub oba wystąpień
f()
z2
i nadal będzie ona działać tak samo:lub
lub
wszyscy będą zachowywać się tak samo.
Właściwie to oszukiwałem. Powinienem być w stanie zastąpić wywołanie do
print
wartością zwracaną (która w ogóle nie jest wartością) bez zmiany znaczenia programu. Jednak wyraźnie, jeśli po prostu usunę te dwieprint
instrukcje, znaczenie programu ulegnie zmianie: wcześniej wypisał coś na ekranie, a potem nie. We / wy nie jest względnie przejrzyste.Prosta zasada brzmi: jeśli możesz zastąpić dowolne wywołanie wyrażenia, podwyrażenia lub podprogramu wartością zwracaną tego wyrażenia, podwyrażenia lub wywołania podprogramu w dowolnym miejscu programu, bez zmiany jego znaczenia, wówczas masz odniesienie przezroczystość. A to oznacza, że praktycznie nie możesz mieć żadnego wejścia / wyjścia, nie możesz mieć żadnego stanu zmiennego, nie może mieć żadnych skutków ubocznych. W każdym wyrażeniu wartość wyrażenia musi zależeć wyłącznie od wartości składowych wyrażenia. I w każdym wywołaniu podprogramu wartość zwracana musi zależeć wyłącznie od argumentów.
źródło
print
przykład. Być może jednym ze sposobów, aby to zobaczyć, jest to, że to, co jest drukowane na ekranie, jest częścią „wartości zwracanej”. Jeśli możesz zastąpićprint
jego funkcją wartością zwracaną i równoważnym zapisem na terminalu, przykład działa.4
i2 + 2
non-wymienny, ponieważ mają różne czasy pracy, a cała punktem referencyjnym przejrzystości jest to, że można zastąpić wyraz z tym, co ocenia się. Ważną kwestią byłoby bezpieczeństwo wątku.listOfSequence.append(n)
powracaNone
, więc powinieneś być w stanie zastąpić każdą rozmowę, abylistOfSequence.append(n)
zNone
nie zmieniając sens swojego programu. Możesz to zrobić? Jeśli nie, to nie jest referencyjnie przejrzysty.Części tej odpowiedzi pochodzą bezpośrednio z niedokończonego samouczka na temat programowania funkcjonalnego , hostowanego na moim koncie GitHub:
Rozważ prosty przykład:
W czysto funkcjonalnym języku lewa i prawa strona znaku równości są wzajemnie zastępowalne na oba sposoby. Oznacza to, że w przeciwieństwie do języka takiego jak C powyższa notacja naprawdę zapewnia równość. Konsekwencją tego jest to, że możemy rozumować o kodzie programu tak samo jak równania matematyczne.
Z Haskell wiki :
Dla kontrastu, rodzaj operacji wykonywanej przez języki podobne do C jest czasami określany jako destrukcyjne zadanie .
Termin czysty jest często używany do opisania właściwości wyrażeń związanych z tą dyskusją. Aby funkcję można było uznać za czystą,
Według metafory czarnej skrzynki, znajdującej się w wielu podręcznikach matematycznych, elementy wewnętrzne funkcji są całkowicie odcięte od świata zewnętrznego. Efektem ubocznym jest sytuacja, gdy funkcja lub wyrażenie narusza tę zasadę - oznacza to, że procedura może w jakiś sposób komunikować się z innymi jednostkami programu (np. W celu udostępniania i wymiany informacji).
Podsumowując, przejrzystość referencyjna jest niezbędna, aby funkcje zachowywały się jak prawdziwe , matematyczne funkcje również w semantyce języków programowania.
źródło
[here](link to source)
...”, po którym następuje 2) właściwe formatowanie cytatu (użyj znaków cudzysłowu lub, jeszcze lepiej,>
symbolu). Nie zaszkodzi również, jeśli oprócz ogólnych wskazówek, odpowiesz na konkretne pytanie, w tym przypadku of(x)+f(x)
/2*f(x)
, zobacz Jak odpowiedzieć - w przeciwnym razie może się wydawać, że po prostu reklamujesz swoją stronę