La parte complessa è il ciclo. Cominciamo da quello. Generalmente un ciclo viene convertito in stile funzionale esprimendo l'iterazione con una singola funzione. Un'iterazione è una trasformazione della variabile del ciclo.
Ecco un'implementazione funzionale di un loop generale:
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
Ci vuole (un valore iniziale della variabile loop, la funzione che esprime una singola iterazione [sulla variabile loop]) (una condizione per continuare il ciclo).
Il tuo esempio utilizza un ciclo su un array, che si rompe anche. Questa capacità nella tua lingua imperativa è cotta nel linguaggio stesso. Nella programmazione funzionale tale capacità viene solitamente implementata a livello di biblioteca. Ecco una possibile implementazione
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))
In esso:
Uso una coppia ((val, next_pos)) che contiene la variabile loop visibile all'esterno e la posizione nell'array, che questa funzione nasconde.
La funzione di iterazione è leggermente più complessa rispetto al ciclo generale, questa versione consente di utilizzare l'elemento corrente dell'array. [È in al curry modulo.]
Tali funzioni sono solitamente denominate "piega".
Ho inserito una "l" nel nome per indicare che l'accumulo degli elementi dell'array è fatto in modo associativo a sinistra; per imitare l'abitudine dei linguaggi di programmazione imperativi per iterare un array dall'indice basso a alto.
Ho inserito una "c" nel nome per indicare che questa versione di fold ha una condizione che controlla se e quando il ciclo deve essere interrotto anticipatamente.
Naturalmente è probabile che tali funzioni di utilità siano prontamente disponibili nella libreria di base fornita con il linguaggio di programmazione funzionale utilizzato. Li ho scritti qui per la dimostrazione.
Ora che abbiamo tutti gli strumenti che sono nella lingua nel caso imperativo, possiamo rivolgerci per implementare la funzionalità specifica del tuo esempio.
La variabile nel tuo ciclo è una coppia ('risposta', un valore booleano che codifica se continuare).
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)
Nota che ho usato una nuova "variabile" "new_answer". Questo perché nella programmazione funzionale non posso modificare il valore di una "variabile" già inizializzata. Non mi preoccupo delle prestazioni, il compilatore può riutilizzare la memoria di "risposta" per "new_answer" tramite l'analisi del tempo di vita, se ritiene che sia più efficiente.
Incorporando questo nella nostra funzione di loop sviluppata in precedenza:
doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)
"Array" qui è il nome del modulo che esporta la funzione foldlc è.
"pugno", "secondo" indicano le funzioni che restituiscono il primo, il secondo componente del suo parametro di coppia
fst : (x, y) -> x
snd : (x, y) -> y
In questo caso, lo stile "point-free" aumenta la leggibilità dell'implementazione di doSomeCalc:
doSomeCalc = Array.foldlc (0, true) iter snd >>> fst
(> > >) è la composizione della funzione: (>>>) : (a -> b) -> (b -> c) -> (a -> c)
È lo stesso di sopra, solo il parametro "arr" è escluso da entrambi i lati dell'equazione che definisce.
Un'ultima cosa: verificare il caso (array == null). Nei linguaggi di programmazione meglio progettati, ma anche in linguaggi mal progettati con una disciplina di base, uno piuttosto usa un tipo facoltativo per esprimere non- esistenza. Questo non ha molto a che fare con la programmazione funzionale, che alla fine riguarda la questione, quindi non la gestisco.