Skomplikowaną częścią jest pętla. Zacznijmy od tego. Pętla jest zwykle konwertowana na styl funkcjonalny poprzez wyrażenie iteracji za pomocą jednej funkcji. Iteracja to transformacja zmiennej pętli.
Oto funkcjonalna implementacja ogólnej pętli:
loop : v -> (v -> v) -> (v -> Bool) -> v
loop init iter cond_to_cont =
if cond_to_cont init
then loop (iter init) iter cond
else init
Trwa (wartość początkowa zmiennej pętli, funkcja wyrażająca pojedynczą iterację [w zmiennej pętli]) (warunek kontynuacji pętli).
W twoim przykładzie użyto pętli na tablicy, która również się psuje. Ta umiejętność w języku imperatywnym jest upieczona w samym języku. W programowaniu funkcjonalnym taka zdolność jest zwykle implementowana na poziomie biblioteki. Oto możliwe wdrożenie
module Array (foldlc) where
foldlc : v -> (v -> e -> v) -> (v -> Bool) -> Array e -> v
foldlc init iter cond_to_cont arr =
loop
(init, 0)
(λ (val, next_pos) -> (iter val (at next_pos arr), next_pos + 1))
(λ (val, next_pos) -> and (cond_to_cont val) (next_pos < size arr))
W tym :
Używam pary ((val, next_pos)), która zawiera zmienną pętli widoczną na zewnątrz i pozycję w tablicy, którą ta funkcja ukrywa.
Funkcja iteracji jest nieco bardziej złożona niż w ogólnej pętli, ta wersja umożliwia użycie bieżącego elementu tablicy. [Jest w formie curry ]
Takie funkcje są zwykle nazywane „fold”.
W nazwie wstawiam „l”, aby wskazać, że akumulacja elementów tablicy odbywa się w lewostronny sposób; naśladować nawyk imperatywnych języków programowania do iteracji tablicy od niskiego do wysokiego indeksu.
Umieszczam „c” w nazwie, aby wskazać, że ta wersja fold przyjmuje warunek kontrolujący, czy i kiedy pętla ma zostać zatrzymana wcześniej.
Oczywiście takie funkcje narzędziowe będą prawdopodobnie łatwo dostępne w bibliotece podstawowej dostarczanej z użytym funkcjonalnym językiem programowania. Napisałem je tutaj dla celów demonstracyjnych.
Teraz, gdy mamy wszystkie narzędzia, które są w języku w bezwzględnie koniecznym przypadku, możemy zwrócić się o wdrożenie konkretnej funkcjonalności twojego przykładu.
Zmienna w Twojej pętli to para („odpowiedź”, boolean, który koduje, czy kontynuować).
iter : (Int, Bool) -> Int -> (Int, Bool)
iter (answer, cont) collection_element =
let new_answer = answer + collection_element
in case new_answer of
10 -> (new_answer, false)
150 -> (new_answer + 100, true)
_ -> (new_answer, true)
Zauważ, że użyłem nowej „zmiennej” „new_answer”. Wynika to z faktu, że w programowaniu funkcjonalnym nie mogę zmienić wartości już zainicjowanej „zmiennej”. Nie martwię się o wydajność, kompilator może ponownie wykorzystać pamięć „odpowiedz” dla „new_answer” poprzez analizę czasu życia, jeśli uzna, że jest to bardziej wydajne.
Włączenie tego do naszej wcześniej opracowanej funkcji pętli:
doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)
„Array” tutaj jest nazwą modułu, którym jest funkcja eksportu foldlc.
„pięść”, „drugi” oznaczają funkcje, które zwracają pierwszy, drugi składnik parametru pary
fst : (x, y) -> x
snd : (x, y) -> y
W tym przypadku styl „bez punktów” zwiększa czytelność implementacji doSomeCalc:
doSomeCalc = Array.foldlc (0, true) iter snd >>> fst
(>>>) to skład funkcji: (>>>) : (a -> b) -> (b -> c) -> (a -> c)
Jest tak samo jak powyżej, tylko parametr „arr” jest pomijany po obu stronach równania definiującego.
I ostatnia rzecz: sprawdzanie wielkości liter (tablica == null). W lepiej zaprojektowanych językach programowania, ale nawet w źle zaprojektowanych językach z pewną podstawową dyscypliną raczej używa się opcjonalnego typu do wyrażenia nieistnienia. Nie ma to wiele wspólnego z programowaniem funkcjonalnym, o które ostatecznie chodzi, więc nie radzę sobie z tym.
break
ireturn answer
można zastąpićreturn
wewnętrzną pętlą. W FP możesz wdrożyć ten wczesny powrót, używając kontynuacji, patrz np. En.wikipedia.org/wiki/Continuation