Складна частина - це петля. Почнемо з цього. Цикл зазвичай перетворюється у функціональний стиль, виражаючи ітерацію однією функцією. Ітерація - це перетворення змінної циклу.
Ось функціональна реалізація загального циклу:
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
Це займає (початкове значення змінної циклу, функція, яка виражає одну ітерацію [на змінній циклу]) (умова продовження циклу).
У вашому прикладі використовується цикл на масиві, який також розбивається. Ця здатність у вашій імперативній мові перетворюється на саму мову. У функціональному програмуванні така можливість зазвичай реалізується на рівні бібліотеки. Ось можлива реалізація
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))
В цьому :
Я використовую пару ((val, next_pos)), яка містить змінну циклу, видиму зовні і положення в масиві, яке приховує ця функція.
Функція ітерації трохи складніша, ніж у загальному циклі, ця версія дає можливість використовувати поточний елемент масиву. [Це у витриманому вигляді.]
Такі функції зазвичай називають "скласти".
Я ставлю "l" в імені, щоб вказати, що накопичення елементів масиву відбувається в ліво-асоціативному вигляді; імітувати звичку імперативних мов програмування для ітерації масиву від низького до високого індексу.
Я поставив "c" в імені, щоб вказати, що ця версія складки має умову, яка контролює, якщо і коли цикл потрібно зупинити достроково.
Звичайно, такі функції утиліти, ймовірно, будуть легко доступні в базовій бібліотеці, що постачається з використовуваною функціональною мовою програмування. Я написав їх тут для демонстрації.
Тепер, коли у нас є всі інструменти, які є в мові в імперативному випадку, ми можемо звернутися до реалізації конкретної функціональності вашого прикладу.
Змінна у вашому циклі - пара ('відповідь', булева, що кодує, чи слід продовжувати).
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)
Зауважте, що я використовував нову "змінну" "new_answer". Це тому, що у функціональному програмуванні я не можу змінити значення вже ініціалізованої "змінної". Я не переживаю за ефективність, компілятор може скористатися повторним використанням пам'яті "відповісти" для "new_answer" за допомогою аналізу життєвого часу, якщо він вважає, що це більш ефективно.
Включення цього в нашу функцію циклу, розроблену раніше:
doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)
"Масив" - це назва модуля, який експортує функцію foldlc.
"кулак", "другий" стенд для функцій, що повертають перший, другий компонент пара його пари
fst : (x, y) -> x
snd : (x, y) -> y
У цьому випадку стиль «без точки» збільшує читабельність реалізації doSomeCalc:
doSomeCalc = Array.foldlc (0, true) iter snd >>> fst
(>>>) - це функціональний склад: (>>>) : (a -> b) -> (b -> c) -> (a -> c)
Це те саме, що вище, просто параметр "arr" залишається з обох сторін визначального рівняння.
Останнє: перевірка наявності (array == null). У кращих мовах програмування, але навіть у погано розроблених мовах з певною базовою дисципліною, скоріше використовується необов'язковий тип для вираження неіснуючого. Це не має великого відношення до функціонального програмування, про яке в кінцевому підсумку йдеться, тому я цим не займаюся.
break
таreturn answer
може бути заміненаreturn
внутрішньою петлею. На FP ви могли реалізувати це раннє повернення, використовуючи продовження, див., Наприклад, en.wikipedia.org/wiki/Continuation