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:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
nie jest referencyjnie przejrzysty, w rzeczywistości dzwoni
foo(x) + foo(x)
i
2 * foo(x)
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
f :: Int -> Int
i dowolną liczbą całkowitą x, zawsze tak jest
2 * (f x) == (f x) + (f x)
Przykładem akcji jest wynik funkcji biblioteki getLine:
getLine :: IO String
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:
[getLine, getLine] :: [IO String]
Działania są wyjątkowe, ponieważ można powiedzieć środowisku wykonawczemu Haskell, aby je wykonał, pisząc:
main = <some action>
W takim przypadku, po uruchomieniu programu Haskell, środowisko wykonawcze przechodzi przez akcję związaną z nią maini 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ć lengthfunkcji:
length "Hello"
zwróci 5. Ale jeśli chcesz znaleźć długość ciągu odczytywanego z terminala, nie możesz pisać
length (getLine)
ponieważ pojawia się błąd typu: lengthoczekuje wprowadzenia listy typów (a String jest rzeczywiście listą), ale getLinejest wartością typu IO String(akcja). W ten sposób system typów zapewnia, że wartość akcji podobna getLine(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-action Int.
EDYTOWAĆ
Aby odpowiedzieć na pytanie exizt, oto mały program Haskell, który odczytuje wiersz z konsoli i drukuje jego długość.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
Główna akcja składa się z dwóch podgrup, które są wykonywane sekwencyjnie:
getlinetypu IO String,
- drugi konstruuje się, oceniając funkcję
putStrLntypu String -> IO ()na podstawie jej argumentu.
Dokładniej, druga akcja jest zbudowana przez
- powiązanie
linez wartością odczytaną przez pierwszą akcję,
- ocena funkcji czystych
length(oblicz długość jako liczba całkowita), a następnie show(zamień liczbę całkowitą na ciąg znaków),
- budowanie akcji poprzez zastosowanie funkcji
putStrLndo wyniku show.
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ć:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
ponieważ show (length line)ma typ, Stringpodczas gdy notacja wymaga, aby po akcji ( getLinetypu IO String) następowała kolejna akcja (np. putStrLn (show (length line))typu IO ()).
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.