What are the functional equivalents of imperative break statements and other loop checks

clojurefunctional programminghaskell

Let's say, I've the below logic. How to write that in Functional Programming?

    public int doSomeCalc(int[] array)
    {
        int answer = 0;
        if(array!=null)
        {
            for(int e: array)
            {
                answer += e;
                if(answer == 10) break;
                if(answer == 150) answer += 100;
            }
        }
        return answer;
    }

The examples in most blogs, articles… I see just explains the simple case of one straight forward math function say 'Sum'.
But, I have a logic similar to the above written in Java and would like to migrate that to functional code in Clojure.
If we can't do the above in FP, then the kind of promotions for FP doesn't state this explicitly.

I know that the above code is totally imperative. It was not written with the forethought of migrating it to FP in future.

Best Answer

The closest equivalent to looping over an array in most functional languages is a fold function, i.e. a function that calls a user-specified function for each value of the array, passing an accumulated value along the chain. In many functional languages, fold is augmented by a variety of additional functions that provide extra features, including the option to stop early when some condition arises. In lazy languages (e.g. Haskell), stopping early can be achieved simply by not evaluating any further along the list, which will cause additional values to never be generated. Therefore, translating your example to Haskell, I would write it as:

doSomeCalc :: [Int] -> Int
doSomeCalc values = foldr1 combine values
  where combine v1 v2 | v1 == 10  = v1
                      | v1 == 150 = v1 + 100 + v2
                      | otherwise = v1 + v2

Breaking this down line by line in case you're not familiar with Haskell's syntax, this works like this:

doSomeCalc :: [Int] -> Int

Defines the type of the function, accepting a list of ints and returning a single int.

doSomeCalc values = foldr1 combine values

The main body of the function: given argument values, return foldr1 called with arguments combine (which we'll define below) and values. foldr1 is a variant of the fold primitive that starts with the accumulator set to the first value of the list (hence the 1 in the function name), then combines it using the user specified function from left to right (which is usually called a right fold, hence the r in the function name). So foldr1 f [1,2,3] is equivalent to f 1 (f 2 3) (or f(1,f(2,3)) in more conventional C-like syntax).

  where combine v1 v2 | v1 == 10  = v1

Defining the combine local function: it receives two arguments, v1 and v2. When v1 is 10, it just returns v1. In this case, v2 is never evaluated, so the loop stops here.

                      | v1 == 150 = v1 + 100 + v2

Alternatively, when v1 is 150, adds an extra 100 to it, and adds v2.

                      | otherwise = v1 + v2

And, if neither of those conditions is true, just adds v1 to v2.

Now, this solution is somewhat specific to Haskell, because the fact that a right fold terminates if the combining function doesn't evaluate its second argument is caused by Haskell's lazy evaluation strategy. I don't know Clojure, but I believe it uses strict evaluation, so I would expect it to have a fold function in its standard library that includes specific support for early termination. This is often called foldWhile, foldUntil or similar.

A quick look at the Clojure library documentation suggests that it is a little different from most functional languages in naming, and that fold isn't what you're looking for (it's a more advanced mechanism aimed at enabling parallel computation) but reduce is the more direct equivalent. Early termination occurs if the reduced function is called within your combining function. I'm not 100% sure I understand the syntax, but I suspect what you're looking for is something like this:

(reduce 
    (fn [v1 v2]
        (if (= v1 10) 
             (reduced v1)
             (+ v1 v2 (if (= v1 150) 100 0))))
    array)

NB: both translations, Haskell and Clojure, are not quite right for this specific code; but they convey the general gist of it -- see discussion in the comments below for specific problems with these examples.

Related Topic