Programowanie funkcjonalne obejmuje wiele różnych technik. Niektóre techniki są w porządku z efektami ubocznymi. Ale jednym ważnym aspektem jest rozumowanie równań : jeśli wywołam funkcję o tej samej wartości, zawsze otrzymam ten sam wynik. Mogę więc zastąpić wywołanie funkcji wartością zwracaną i uzyskać równoważne zachowanie. Ułatwia to rozumowanie programu, zwłaszcza podczas debugowania.
Jeśli funkcja ma skutki uboczne, nie do końca to działa. Zwracana wartość nie jest równoważna wywołaniu funkcji, ponieważ zwracana wartość nie zawiera skutków ubocznych.
Rozwiązanie polega na wyłączeniu niepożądane efekty, kodującego te efekty w wartości powrotnej . Różne języki mają różne systemy efektów. Np. Haskell używa monad do kodowania niektórych efektów, takich jak IO lub mutacja stanu. Języki C / C ++ / Rust mają system typów, który może uniemożliwić mutację niektórych wartości.
W języku imperatywnym print("foo")
funkcja wydrukuje coś i niczego nie zwróci. W czysto funkcjonalnym języku, takim jak Haskell, print
funkcja przyjmuje również obiekt reprezentujący stan świata zewnętrznego i zwraca nowy obiekt reprezentujący stan po wykonaniu tego wyniku. Coś podobnego do newState = print "foo" oldState
. Mogę utworzyć tyle nowych stanów ze starego stanu, ile mi się podoba. Jednak tylko jedna będzie kiedykolwiek używana przez funkcję główną. Muszę więc zsekwencjonować stany z wielu akcji, łącząc łańcuchy funkcji. Aby wydrukować foo bar
, mógłbym powiedzieć coś takiego print "bar" (print "foo" originalState)
.
Jeśli stan wyjściowy nie jest używany, Haskell nie wykonuje działań prowadzących do tego stanu, ponieważ jest to leniwy język. I odwrotnie, to lenistwo jest możliwe tylko dlatego, że wszystkie efekty są wyraźnie zakodowane jako wartości zwracane.
Pamiętaj, że Haskell jest jedynym powszechnie używanym językiem funkcjonalnym, który korzysta z tej trasy. Inne języki funkcjonalne, w tym rodzina Lisp, rodzina ML i nowsze języki funkcjonalne, takie jak Scala, zniechęcają, ale dopuszczają wciąż skutki uboczne - można je nazwać językami imperatywno-funkcjonalnymi.
Korzystanie ze skutków ubocznych we / wy jest prawdopodobnie w porządku. Często operacje we / wy (inne niż logowanie) są wykonywane tylko na zewnętrznej granicy systemu. W logice biznesowej nie zachodzi żadna komunikacja zewnętrzna. Następnie można napisać rdzeń oprogramowania w czystym stylu, jednocześnie wykonując nieczyste operacje we / wy w zewnętrznej powłoce. Oznacza to również, że rdzeń może być bezpaństwowy.
Bezpaństwowość ma wiele praktycznych zalet, takich jak zwiększona racjonalność i skalowalność. Jest to bardzo popularne w backendach aplikacji internetowych. Każdy stan jest przechowywany na zewnątrz, we wspólnej bazie danych. Ułatwia to równoważenie obciążenia: nie muszę przyklejać sesji do konkretnego serwera. Co jeśli potrzebuję więcej serwerów? Po prostu dodaj kolejny, ponieważ używa tej samej bazy danych. Co się stanie, jeśli jeden serwer ulegnie awarii? Mogę ponowić wszelkie oczekujące żądania na innym serwerze. Oczywiście nadal istnieje stan - w bazie danych. Ale wyraziłem to jasno i wyodrębniłem, i jeśli mogę, mogę zastosować podejście czysto funkcjonalne wewnętrznie.