Powiemy, że język funkcyjny jest czysty (ang. pure) jeśli funkcje nie mogą mieć efektów ubocznych (zmiana stanu, wywołania systemowe), a jedyny sposób, żeby te efekty osiągnąć jest przez wykorzystanie monad. Planując architekturę języka Great# postanowiłem, że jego funkcyjna część będzie właśnie czysta.
Miałem okazję programować w kilku językach funkcyjnych, m.in. czystym Haskellu i nieczystym F#. Głównym powodem dla którego chcielibyśmy korzystać z czystego języka jest to, że niezależnie ile razy wywołamy funkcję o danym parametrze, to zawsze dostaniemy dokładnie taki sam wynik, a stan aplikacji nie zostanie zmieniony. Co więcej pozwala to na zmianę kolejności wywoływania funkcji (jeśli argumentem jednej nie jest wynik poprzedniej). Jeśli funkcja polegałaby na pewnym stanie, który być może jest modyfikowany przez inną funkcję, to zmiana kolejności ich wywołania mogłaby skutkować błędami wykonania algorytmu.
Czystość funkcji pozwala na łatwiejsze określenie co funkcja robi, a dzięki temu łatwiej program zrozumieć i jest mniejsza potrzeba na debugowanie.
Dlatego w Great# funkcje, które będziemy pisać poza klasami (w przeciwieństwie do metod), muszą być czyste. Jak to sprawdzić?
Zakładamy, że funkcja jest czysta, jeśli używa przypisań tylko do lokalnie utworzonych zmiennych i stałych i wywołuje tylko czyste funkcje.
Oznacza to, że dla funkcji, która przyjmuje obiekt klasy A
o metodzie GetId()
, będziemy mogli tę metodę wywołać pod warunkiem, że jedyne co ona robi to return this.id
(zgodnie z powyższą definicją).
Czyli, żeby sprawdzić czy funkcja jest czysta, będziemy rekurencyjnie wchodzić w funkcje, które wywołuje i sprawdzać czy są czyste. Dla zwiększenia wydajności będzie ta informacja spamiętywana.
Mając czyste funkcje i niekoniecznie czyste metody zachęcimy do takiego wzorca: program jest obiektowy, ale korzysta z funkcyjnych algorytmów, które jeśli muszą uzyskiwać efekty uboczne to wykorzystują monady.