Programowanie funkcjonalne w Scali wyjaśnia wpływ efektu ubocznego na przełamanie przejrzystości referencyjnej:
efekt uboczny, co oznacza pewne naruszenie przejrzystości referencyjnej.
Przeczytałem część SICP , która omawia użycie „modelu zastępczego” do oceny programu.
Ponieważ z grubsza rozumiem model podstawienia z referencyjną przezroczystością (RT), możesz rozłożyć funkcję na jej najprostsze części. Jeśli wyrażenie ma wartość RT, możesz go zdekomponować i zawsze uzyskać ten sam wynik.
Jednak, jak stwierdza powyższy cytat, stosowanie efektów ubocznych może / spowoduje uszkodzenie modelu substytucyjnego.
Przykład:
val x = foo(50) + bar(10)
Jeśli foo
i bar
nie mają skutków ubocznych, wykonanie dowolnej funkcji zawsze zwróci ten sam wynik x
. Ale jeśli mają skutki uboczne, zmienią zmienną, która zakłóca / wrzuca klucz do modelu substytucji.
Czuję się swobodnie z tym wyjaśnieniem, ale nie do końca go rozumiem.
Proszę mnie poprawić i wypełnić wszelkie dziury w odniesieniu do skutków ubocznych przełamujących RT, omawiając również wpływ na model substytucyjny.
źródło
RT
wyłącza was z pomocąsubstitution model.
duży problem ze nie jest w stanie użyćsubstitution model
jest moc używania rozumu o programie?Wyobraź sobie, że próbujesz zbudować ścianę i otrzymałeś asortyment pudełek o różnych rozmiarach i kształtach. Musisz wypełnić konkretny otwór w kształcie litery L w ścianie; powinieneś poszukać pudełka w kształcie litery L, czy możesz zastąpić dwa proste pudełka o odpowiednim rozmiarze?
W świecie funkcjonalnym odpowiedź brzmi: każde z tych rozwiązań będzie działać. Budując funkcjonalny świat, nigdy nie musisz otwierać skrzynek, aby zobaczyć, co jest w środku.
W świecie imperatywnym niebezpieczne jest budowanie ściany bez sprawdzania zawartości każdego pudełka i porównywania ich z zawartością każdego innego pudełka:
Myślę, że przestanę, zanim zmarnuję twój czas na bardziej nieprawdopodobne metafory, ale mam nadzieję, że o to chodzi; funkcjonalne klocki nie zawierają ukrytych niespodzianek i są całkowicie przewidywalne. Ponieważ zawsze możesz użyć mniejszych bloków o odpowiednim rozmiarze i kształcie, aby zastąpić większy i nie ma różnicy między dwoma polami o tym samym rozmiarze i kształcie, masz przejrzystość referencyjną. W przypadku cegieł imperatywnych nie wystarczy mieć coś odpowiedniego rozmiaru i kształtu - musisz wiedzieć, jak zbudowano cegłę. Nie referencyjnie przejrzysty.
W czysto funkcjonalnym języku wszystko, co musisz zobaczyć, to podpis funkcji, aby wiedzieć, co ona robi. Oczywiście możesz zajrzeć do środka, aby zobaczyć, jak dobrze sobie radzi, ale nie musisz patrzeć.
W imperatywnym języku nigdy nie wiesz, jakie niespodzianki mogą kryć się w środku.
źródło
(a, b) -> a
może być tylkofst
funkcją i że funkcja typua -> a
może być tylkoidentity
funkcją, ale niekoniecznie można powiedzieć(a, a) -> a
na przykład o funkcji typu .Tak, intuicja jest całkiem słuszna. Oto kilka wskazówek, aby uzyskać bardziej precyzyjne:
Jak powiedziałeś, każde wyrażenie RT powinno mieć
single
„wynik”. To znaczy, biorąc pod uwagęfactorial(5)
wyrażenie w programie, powinno zawsze dawać ten sam „wynik”. Tak więc, jeśli pewnafactorial(5)
jest w programie i daje 120, to zawsze powinna dawać 120, niezależnie od tego, która „kolejność kroków” jest rozszerzana / obliczana - niezależnie od czasu .Przykład:
factorial
funkcja.Istnieje kilka uwag do tego wyjaśnienia.
Przede wszystkim należy pamiętać, że różne modele oceny (patrz kolejność aplikacyjna vs. normalna) mogą dawać różne „wyniki” dla tego samego wyrażenia RT.
W powyższym kodzie
first
isecond
są referencyjnie przezroczyste, a jednak wyrażenie na końcu daje różne „wyniki”, jeśli są oceniane w normalnej kolejności i kolejności stosowania (w tym drugim przypadku wyrażenie się nie zatrzymuje)..... co prowadzi do użycia „wyniku” w cudzysłowie. Ponieważ wyrażenie nie jest wymagane do zatrzymania, może nie wygenerować wartości. Zatem użycie „wyniku” jest niejasne. Można powiedzieć, że wyrażenie RT zawsze daje to samo
computations
w modelu oceny.Po trzecie, może być wymagane zobaczenie dwóch
foo(50)
pojawiających się w programie w różnych lokalizacjach jako różnych wyrażeń - każdy z nich daje własne wyniki, które mogą się od siebie różnić. Na przykład, jeśli język dopuszcza zakres dynamiczny, oba wyrażenia, choć leksykalnie identyczne, są różne. W perlu:Zakres dynamiczny wprowadza w błąd, ponieważ ułatwia myślenie, że
x
jest to jedyny wkładfoo
, podczas gdy w rzeczywistości jestx
iy
. Jednym ze sposobów dostrzeżenia różnicy jest przekształcenie programu w równoważny bez zakresu dynamicznego - to znaczy przekazanie jawnie parametrów, więc zamiast definiowaćfoo(x)
, definiujemyfoo(x, y)
i przekazujemyy
jawnie w wywołujących.Chodzi o to, że zawsze jesteśmy
function
nastawieni na myślenie: biorąc pod uwagę pewien wkład wyrażenia, otrzymujemy odpowiedni „wynik”. Jeśli podamy ten sam wkład, zawsze powinniśmy oczekiwać tego samego „wyniku”.A co z następującym kodem?
foo
Postępowanie łamie RT ponieważ istnieje redefinicje. Oznacza to, że zdefiniowaliśmyy
w jednym punkcie, a następnie zdefiniowaliśmy to samoy
. W powyższym przykładzie perlay
s są różnymi powiązaniami, chociaż mają tę samą literę o nazwie „y”. Tutajy
są w rzeczywistości takie same. Dlatego mówimy, że (ponowne) przypisanie jest metaoperacją : w rzeczywistości zmieniasz definicję swojego programu.Z grubsza ludzie zwykle przedstawiają różnicę w następujący sposób: w ustawieniu bez efektów ubocznych masz mapowanie od
input -> output
. W ustawieniu „imperatywnym” maszinput -> ouput
w kontekście coś,state
co może się zmieniać w czasie.Teraz zamiast po prostu zastępować wyrażenia odpowiadającymi im wartościami, należy również zastosować transformacje do
state
każdej operacji, która tego wymaga (i oczywiście wyrażenia mogą się z nimi konsultować,state
aby wykonać obliczenia).Tak więc, jeśli w programie wolnym od skutków ubocznych wszystko, co musimy wiedzieć, aby obliczyć wyrażenie, to jego indywidualne dane wejściowe, w programie imperatywnym musimy znać dane wejściowe i cały stan dla każdego kroku obliczeniowego. Rozumowanie jest pierwszym, które cierpi z powodu dużego ciosu (teraz, aby debugować problematyczną procedurę, potrzebujesz danych wejściowych i zrzutu pamięci). Niektóre triki są niepraktyczne, jak na przykład zapamiętywanie. Ale współbieżność i równoległość stają się znacznie trudniejsze.
źródło