Czepiając się bardzo - oczywioście funkcje czysto czyste prawie nie istnieją, bo o ile są ewaluowane to mamy takie obserwowalne efekty jak zużcie prądu, podgrzenie procka, zmiany liczników CPU, być może jakieś alokacje w pamięci.
Natomiast praktyczna denicja jest taka, że funkcja czysta to taka, gdzie wynik zalezy tylko od argumentów. I patrzymy na rzeczy istotne z punktu widzenia logiki systemu. Podgrzewanie procka zwykle nie jest istotne. Mówi się wtedy, że nie ma efektów ubocznych.
Przy czym w Haskellu czy Scali funkcje, postaci f :: x -> IO ()
, def f [X] (x:X) : IO[Unit]
są w istocie czyste. Nie mają efektów ubocznych.
Może w środku jest kod pisania na ekran i pobierania z bazy danych, ale funkcja jest zupełnie czysta (o ile oczywiście programista na siłe nie oszukał, co w Scali zrobić łatwo, a w Haskellu trudno).
To tylko pozorny paradoks - tak działa monada IO. Łatwo się o tej czystości przekonać jeśli wynik takiej funkcj przepuścić przez dziwaczną funkcję typu: whatever :: a -> ()
.
f :: IO ()
f = do
putStrLn "Write something"
x <- getLine
putStrLn ("Input was " ++ x)
whatever :: a -> ()
whatever _ = ()
main = f -- wyświetli się co trzeba
-- main = return $ whatever f -- ciekawostka
Efekty są po prostu zakodowane w rezultacie (IO
), a sama funkcja nic ubocznego
nie robi.
Dzieki temu o wiele łatwiej jest analizować i testować programy.
W językach imperatywnych można przyjąc, że każda funkcja ma ukryte dodatkowe argumenty i rezultaty czyli zamiast f :: a ->b
mamy f:: (a,STATE, IO) -> (b, STATE, IO)
. Gdzie STATE to jakiś globalny widzialny stan, a IO to wykonywane operacje wejścia/wyjścia. Nie dość ,że to od razu o wiele więcej potencjalnych powiązań do analizy to jeszcze utrudnia testowanie. Ale najgorsze jest to, że nie ma możliwości (tak jak w językach funkcyjnych) olania, cofnięcia zmian na IO. Po wykonaniu funkcji mleko się rozlało, baza dostała INSERTA a STATE sie zmienił i nie ma żadnego prostego rollbacka.
Z jednym się zgadzam: programownaie funkcyjne to właśnie programowanie z efektami ubocznymi. Z kontrolowanymi efektami ubocznymi. Przy czym są one kontrolowalne tak długo jak nasze funkcje efektów ubocznych nie mają.