Question

I'm trying to write a reduce function of type [a -> a -> a] -> [a] -> a which would fold a list of n values with a list of n - 1 binary operators, as in the following example:

reduce [(+), (*), (-)] [2, 3, 5, 7]

Running this example should return 18, and be the equivalent of the following expression:

(((2 + 3) * 5) - 7)

Implementing this function with recursion turns out to be very simple:

reduce (f : fs) (x1 : x2 : xs) = reduce fs (f x1 x2 : xs)

The concept of this function being very close to left folding, with the only difference that it uses multiple operators instead of a single one, I thought it could also be implemented using foldl or equivalent higher order functions, instead of using explicit recursion.

With this in mind, and using applicative functions, I came up with the following implementation:

reduce fs (x : xs) =  foldl (flip id) x (getZipList $ ZipList fs <*> ZipList xs)

As relying on ZipList seems quite verbose and redundant, I'm now wondering if there are nicer implementations for this function, with the same approach of not using explicit recursion.

Was it helpful?

Solution

zipWith is your missing function:

reduce fs (x:xs) = foldl (flip id) x $ zipWith flip fs xs

Basically, create a list of single argument functions by combining the operators with the input values (using flip so the value is used as the second argument rather than the first). The rest is the same as your function.

Just a design note, currently the function isn't total - if you pass an empty list for the values the only value that can be returned is bottom (ie undefined). This is because there is no way to create an a value. A function like this may be better:

reduce2 :: [a -> b -> a] -> a -> [b] -> a
reduce2 fs x xs = foldl (flip id) x $ zipWith flip fs xs

Since the first value is passed separately, either of the lists can be empty and the function will still complete (it will just ignore values it doesn't need if one of the lists is shorter than the other). It is also more general, in that the list of values may have a different type to the result of the function.

OTHER TIPS

How about:

reduce :: [a->a->a] -> [a] -> a
reduce ops args = 
  let ops' = map flip ops
      appliedOps = zipWith ($) ops' $ tail args
  in foldl' (flip ($)) (head args) appliedOps

Also see this question about function application in Haskell. Notice that last answer actually uses ZipList with Control.Applicative.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top