The intricate part is the loop. Let us start with that. A loop is usually converted to functional style by expressing the iteration with a single function. An iteration is a transformation of the loop variable.
Here is a functional implementation of a general loop :
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
It takes (an initial value of the loop variable, the function that expresses a single iteration [on the loop variable]) (a condition to continue the loop).
Your example uses a loop on an array, which also breaks. This capability in your imperative language is baked into the language itself. In functional programming such capability is usually implemented at the library level. Here is a possible implementation
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 it :
I use a ((val, next_pos)) pair which contains the loop variable visible outside and the position in the array, which this function hides.
The iteration function is slightly more complex than in the general loop, this version makes it possible to use the current element of the array. [It is in curried form.]
Such functions are usually named "fold".
I put an "l" in the name to indicate that the accumulation of the elements of the array is done in a left-associative manner; to mimic the habit of imperative programming languages to iterate an array from low to high index.
I put a "c" in the name to indicate that this version of fold takes a condition that controls if and when the loop to be stopped early.
Of course such utility functions are likely to be readily available in the base library shipped with the functional programming language used. I wrote them here for demonstration.
Now that we have all the tools that are in the language in the imperative case, we can turn to implement the specific functionality of your example.
The variable in your loop is a pair ('answer', a boolean that encodes whether to continue).
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)
Note that i used a new "variable" 'new_answer'. This is because in functional programming i can not change the value of an already initialized "variable". I do not worry about performance, the compiler may get to reuse the memory of 'answer' for 'new_answer' via life-time analysis, if it thinks that is more efficient.
Incorporating this into our loop function developed earlier :
doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)
"Array" here is the module name which exports function foldlc is.
"fist", "second" stand for functions that returns the first, second component of its pair parameter
fst : (x, y) -> x
snd : (x, y) -> y
In this case "point-free" style increases the readability of the implementation of doSomeCalc:
doSomeCalc = Array.foldlc (0, true) iter snd >>> fst
(>>>) is function composition : (>>>) : (a -> b) -> (b -> c) -> (a -> c)
It is the same as above, just the "arr" parameter is left out from both sides of the defining equation.
One last thing : checking for case (array == null). In better designed programming languages, but even in badly designed languages with some basic discipline one rather uses an optional type to express non-existence. This does not have much to do with functional programming, which the question is ultimately about, thus i do not deal with it.
break
andreturn answer
can be replaced by areturn
inside the loop. In FP you could implement this early return using continuations, see e.g. en.wikipedia.org/wiki/ContinuationtakeWhile
.