A parte intricada é o loop. Vamos começar com isso. Um loop geralmente é convertido em estilo funcional, expressando a iteração com uma única função. Uma iteração é uma transformação da variável de loop.
Aqui está uma implementação funcional de um loop geral:
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
É preciso (um valor inicial da variável do loop, a função que expressa uma única iteração [na variável do loop]) (uma condição para continuar o loop).
Seu exemplo usa um loop em uma matriz, que também quebra. Esse recurso em sua linguagem imperativa é incorporado à própria linguagem. Na programação funcional, esse recurso geralmente é implementado no nível da biblioteca. Aqui está uma possível implementação
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))
Nisso :
Eu uso um par ((val, next_pos)) que contém a variável de loop visível do lado de fora e a posição na matriz, que esta função oculta.
A função de iteração é um pouco mais complexa do que no loop geral; esta versão possibilita o uso do elemento atual da matriz. [Está na forma de caril .]
Tais funções são geralmente denominadas "fold".
Coloquei um "l" no nome para indicar que o acúmulo dos elementos da matriz é feito de maneira associativa à esquerda; imitar o hábito de linguagens de programação imperativas para iterar uma matriz de baixo para alto índice.
Coloquei um "c" no nome para indicar que esta versão do fold adota uma condição que controla se e quando o loop deve ser interrompido mais cedo.
É claro que essas funções utilitárias provavelmente estarão prontamente disponíveis na biblioteca base fornecida com a linguagem de programação funcional usada. Eu os escrevi aqui para demonstração.
Agora que temos todas as ferramentas que estão na linguagem no caso imperativo, podemos recorrer à implementação da funcionalidade específica do seu exemplo.
A variável no seu loop é um par ('resposta', um booleano que codifica se deve continuar).
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)
Observe que eu usei uma nova "variável" 'new_answer'. Isso ocorre porque na programação funcional não posso alterar o valor de uma "variável" já inicializada. Não me preocupo com o desempenho, o compilador pode reutilizar a memória da 'resposta' para 'new_answer' através da análise do tempo de vida, se achar que é mais eficiente.
Incorporando isso em nossa função de loop desenvolvida anteriormente:
doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)
"Matriz" aqui é o nome do módulo que exporta a função foldlc.
"punho", "segundo" representam funções que retornam o primeiro, segundo componente de seu parâmetro de par
fst : (x, y) -> x
snd : (x, y) -> y
Nesse caso, o estilo "sem ponto" aumenta a legibilidade da implementação do doSomeCalc:
doSomeCalc = Array.foldlc (0, true) iter snd >>> fst
(>>>) é a composição da função: (>>>) : (a -> b) -> (b -> c) -> (a -> c)
É o mesmo que acima, apenas o parâmetro "arr" é deixado de fora dos dois lados da equação que define.
Uma última coisa: verificar caso (array == null). Em linguagens de programação melhor projetadas, mas mesmo em linguagens mal projetadas, com alguma disciplina básica, utiliza-se um tipo opcional para expressar inexistência. Isso não tem muito a ver com programação funcional, que é a questão final, portanto, eu não lido com ela.
break
ereturn answer
pode ser substituída por uma partereturn
interna do loop. No FP, você pode implementar esse retorno antecipado usando continuações, por exemplo, en.wikipedia.org/wiki/Continuation