Writing Folds Using Foldr

In Real World Haskell , chapter 4. " Functional programming" :

Write fold with fold:

-- file: ch04/Fold.hs myFoldl :: (a -> b -> a) -> a -> [b] -> a myFoldl fz xs = foldr step id xs z where step xga = g (fax) 

The above code confused me a lot, and someone named dps rewrote it with a meaningful name to make it a little clearer:

 myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL where stepR lastL accR accInitL = accR (stepL accInitL lastL) 

Someone else, Jeff G, did a great job providing an example and demonstrating the basic mechanism step by step:

 myFoldl (+) 0 [1, 2, 3] = (foldR step id [1, 2, 3]) 0 = (step 1 (step 2 (step 3 id))) 0 = (step 1 (step 2 (\a3 -> id ((+) a3 3)))) 0 = (step 1 (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2))) 0 = (\a1 -> (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2)) ((+) a1 1)) 0 = (\a1 -> (\a2 -> (\a3 -> (+) a3 3) ((+) a2 2)) ((+) a1 1)) 0 = (\a1 -> (\a2 -> (+) ((+) a2 2) 3) ((+) a1 1)) 0 = (\a1 -> (+) ((+) ((+) a1 1) 2) 3) 0 = (+) ((+) ((+) 0 1) 2) 3 = ((0 + 1) + 2) + 3 

But I still can not fully understand, here are my questions:

  1. What is the id function for? What is the role? Why do we need it here?
  2. In the above example, is the id function an accumulator in the lambda function?
  3. The foldr prototype is foldr :: (a -> b -> b) -> b -> [a] -> b , and the first parameter is a function that needs two parameters, but the step function in the implementation of myFoldl uses 3 parameters, I'm completely confused!
+72
recursion haskell fold
May 30 '11 at 3:20
source share
7 answers

Some explanation is ok!

What is the id function for? What is the role? Why do we need it here?

id is an identical function , id x = x and is used as the equivalent of zero when constructing a chain of functions with the composition of functions (.) . You can find it in the prelude .

In the above example, is the id function an accumulator in the lambda function?

Battery is a feature that is created through multiple applications. There is no explicit lambda, as we call the battery, step . You can write it with lambda if you want:

 foldl fa bs = foldr (\bgx -> g (fxb)) id bs a 

Or, as Graham Hatton wrote :

5.1 foldl operator

Now let's suml example and consider the standard foldl operator that processes list items in order from left to right, using the f function to combine the values, and the v value as the initial value:

 foldl :: (β → α → β) → β → ([α] → β) foldl fv [ ] = v foldl fv (x : xs) = foldl f (fvx) xs 

Using this operator, suml can be overridden simply by suml = foldl (+) 0 . Many other functions can be defined in a simple way with foldl . For example, the standard reverse function can be overridden with foldl as follows:

 reverse :: [α] → [α] reverse = foldl (λxs xx : xs) [ ] 

This definition is more efficient than our original definition using fold, as it avoids the use of the inefficient append (++) operator for lists.

A simple generalization of the calculation in the previous section for the suml function shows how to override the foldl function in terms of fold :

 foldl fv xs = fold (λx g → (λa → g (fax))) id xs v 

In contrast, it is not possible to redefine fold in terms of foldl , because foldl is strict at the tail of the list argument, but fold is not. There are a number of useful “duality theorems” regarding fold and foldl , as well as some recommendations for determining which operator is best for specific applications (Bird, 1998).

foldr - foldr :: (a → b → b) → b → [a]

A Haskell programmer would say that the type of foldr is (a → b → b) → b → [a] → b .

and the first parameter is a function that needs two parameters, but the step function in the implementation of myFoldl uses 3 parameters, I'm completely confused

This is confusing and magical! We play the trick and replace the battery with a function that, in turn, applies to the original value to get the result.

Graham Hatton explains the trick to turn foldl into foldr in the above article. Let's start by writing the recursive definition of foldl :

 foldl :: (a -> b -> a) -> a -> [b] -> a foldl fv [] = v foldl fv (x : xs) = foldl f (fvx) xs 

And then reformat it by converting the static argument to f :

 foldl :: (a -> b -> a) -> a -> [b] -> a foldl fv xs = g xs v where g [] v = v g (x:xs) v = g xs (fvx) 

Now rewrite g to swim v inward:

 foldl fv xs = g xs v where g [] = \v -> v g (x:xs) = \v -> g xs (fvx) 

This is the same as thinking of g as a function of a single argument, which returns a function:

 foldl fv xs = g xs v where g [] = id g (x:xs) = \v -> g xs (fvx) 

Now we have g , a function that recursively scans the list, applies some function f . The final value is an identification function, and each step also leads to a function.

But, we already have a very similar recursive function in lists, foldr !

2 Bend Operator

The fold operator is based on the theory of recursion (Kleene, 1952), while the use of fold as a central concept in a programming language goes back to the APL abbreviation operator (Iverson, 1962) and then to the FP insertion operator (Backus, 1978). In Haskell, the fold operator for lists can be defined as follows:

 fold :: (α → β → β) → β → ([α] → β) fold fv [ ] = v fold fv (x : xs) = fx (fold fv xs) 

That is, given the function f type α → β → β and the value of v type β , the function fold fv processes a list of type [α] to give a value of type β , replacing the constructor nil [] with the end of the list by value v and each constructor cons (:) in the list by f . Thus, the fold operator encapsulates a simple recursion pattern for processing lists, in which two constructors for lists are simply replaced with other values ​​and functions. A number of familiar functions in lists have a simple definition using fold .

This is similar to a very similar recursive scheme of our g function. Now the trick: using all the available magic at hand (just like Bird, Meertens and Malcolm), we apply a special rule, the universal property fold , which is the equivalent of two definitions for the function g that processes lists, like:

 g [] = v g (x:xs) = fx (g xs) 

if and only if

 g = fold fv 

So, the universal property of folds states that:

  g = foldr kv 

where g should be equivalent to two equations, for some k and v :

  g [] = v g (x:xs) = kx (g xs) 

From our early foldl constructs, we know v == id . However, for the second equation, we need to calculate the definition of k :

  g (x:xs) = kx (g xs) <=> g (x:xs) v = kx (g xs) v -- accumulator of functions <=> g xs (fvx) = kx (g xs) v -- definition of foldl <= g' (fvx) = kxg' v -- generalize (g xs) to g' <=> k = \xg' -> (\a -> g' (fvx)) -- expand k. recursion captured in g' 

Which, replacing our calculated definitions of k and v gives the definition of foldl as:

 foldl :: (a -> b -> a) -> a -> [b] -> a foldl fv xs = foldr (\xg -> (\a -> g (fvx))) id xs v 

The recursive g is replaced by the foldr combinator, and the accumulator becomes a function constructed by the composition chain f for each element of the list, in the reverse order (therefore, we add the left rather than the right).

This is definitely somewhat advanced, therefore, to deeply understand this transformation, the universal property of folds that makes the transformation possible, I recommend the Hatton tutorial below.




Recommendations

+92
May 30 '11 at 4:23 a.m.
source share

Consider the foldr type:

 foldr :: (b -> a -> a) -> a -> [b] -> a 

While the type step is something like b -> (a -> a) -> a -> a . Since the step goes to foldr , we can conclude that in this case the fold is of type type (b -> (a -> a) -> (a -> a)) -> (a -> a) -> [b] -> (a -> a) .

Do not confuse different values ​​of a in different signatures; it's just a type variable. Also keep in mind that the function arrow is associative, so a -> b -> c is the same as a -> (b -> c) .

So yes, the accumulator value for foldr is a function of type a -> a , and the initial value is id . This makes sense because id is a function that does nothing - for the same reason, you start from scratch as the initial value when you add all the values ​​to the list.

As for step with three arguments, try rewriting it like this:

 step :: b -> (a -> a) -> (a -> a) step xg = \a -> g (fax) 

Did this help to understand what was happening? It takes an extra parameter because it returns a function, and the two ways of writing it are equivalent. Also note the optional parameter after foldr : (foldr step id xs) z . The part in parentheses is the fold itself, which returns a function that is then applied to z .

+9
May 30 '11 at 3:56 a.m.
source share

(quickly review my answers [1] , [2] , [3] , [4] to make sure you understand the Haskell syntax, higher-order functions, currying, function composition, the $ operator, infix / prefix operators, sections and lambdas)

Universal property fold

A fold is simply a codification of certain types of recursion. And the universality property simply states that if your recursion conforms to a specific form, it can be transformed into a fold in accordance with some formal rules. Conversely, each fold can be converted to such a recursion. Once again, some recursions can be translated into folds that give exactly the same answer, and some recursions cannot, and there is an exact procedure for this.

Basically, if your recursive function works in lists, looks like on the left, you can convert it to collapse one on the right, replacing f and v with what actually exists.

 g [] = v ⇒ g (x:xs) = fx (g xs) ⇒ g = foldr fv 

For example:

 sum [] = 0 {- recursion becomes fold -} sum (x:xs) = x + sum xs ⇒ sum = foldr 0 (+) 

Here v = 0 and sum (x:xs) = x + sum xs equivalent to sum (x:xs) = (+) x (sum xs) , therefore f = (+) . 2 more examples

 product [] = 1 product (x:xs) = x * product xs ⇒ product = foldr 1 (*) length [] = 0 length (x:xs) = 1 + length xs ⇒ length = foldr (\_ a -> 1 + a) 0 

The exercise:

  • Solve map , filter , reverse , concat and concatMap recursively, like the above functions on the left side.

  • Convert these 5 functions to foldr according to the formula above , i.e. substituting f and v in the fold formula on the right.

Foldl via foldr

How to write a recursive function that sums numbers from left to right?

 sum [] = 0 -- given `sum [1,2,3]` expands into `(1 + (2 + 3))` sum (x:xs) = x + sum xs 

The first recursive function that comes to find is completely expanded before it even begins to take shape, this is not what we need. One approach is to create a recursive function with an accumulator that immediately adds numbers at each step (read about the recursion tail to learn more about recursion strategies):

 suml :: [a] -> a suml xs = suml' xs 0 where suml' [] n = n -- auxiliary function suml' (x:xs) n = suml' xs (n+x) 

Ok, stop it! Run this code in GHCi and make sure you understand how it works, then continue carefully and thoughtfully. suml cannot be overridden with a fold, but suml' can be.

 suml' [] = v -- equivalent: vn = n suml' (x:xs) n = fx (suml' xs) n 

suml' [] n = n from the function definition, right? And v = suml' [] from the universal property formula. Together, this gives vn = n , a function that immediately returns everything it receives: v = id . Let calculate f :

 suml' (x:xs) n = fx (suml' xs) n -- expand suml' definition suml' xs (n+x) = fx (suml' xs) n -- replace `suml' xs` with `g` g (n+x) = fxgn 

Thus, suml' = foldr (\xgn -> g (n+x)) id and thus suml = foldr (\xgn -> g (n+x)) id xs 0 .

 foldr (\xgn -> g (n + x)) id [1..10] 0 -- return 55 

Now we just need to generalize, replace + with a variable function:

 foldl fa xs = foldr (\xgn -> g (n `f` x)) id xs a foldl (-) 10 [1..5] -- returns -5 

Conclusion

Now read the Graham Hutton Tutorial on versatility and expressiveness fold . Take a pen and paper, try to understand everything that he writes, until you get most of the folds yourself. Don’t sweat if you don’t understand something, you can always come back later, but don’t put off too much.

+5
Feb 26 '15 at 10:50
source share

Here is my proof that foldl can be expressed through foldr , which I find quite simple, apart from the spaghetti name that the step function introduces.

The proposition is that foldl fz xs equivalent

 myfoldl fz xs = foldr step_f id xs z where step_f xga = g (fax) 

The first thing to notice here is that the right side of the first line is actually evaluated as

 (foldr step_f id xs) z 

since foldr accepts only three parameters. This already hints that foldr will not calculate the value, but the curry function, which is then applied to z . There are two cases to find out if myfoldl foldl :

  • Base case: empty list

      myfoldl fz [] = foldr step_f id [] z (by definition of myfoldl) = id z (by definition of foldr) = z foldl fz [] = z (by definition of foldl) 
  • Non-empty list

      myfoldl fz (x:xs) = foldr step_f id (x:xs) z (by definition of myfoldl) = step_f x (foldr step_f id xs) z (-> apply step_f) = (foldr step_f id xs) (fzx) (-> remove parentheses) = foldr step_f id xs (fzx) = myfoldl f (fzx) xs (definition of myfoldl) foldl fz (x:xs) = foldl f (fzx) xs 

Since in 2. the first and last lines are the same in both cases, it can be used to collapse the list to xs == [] , and in this case 1. guarantees the same result. So, by induction, myfoldl == foldl .

+4
09 Oct '12 at 22:20
source share

There is no royal path to mathematics or even through Haskell. Let be

 hz = (foldr step id xs) z where step xg = \a -> g (fax) 

What the hell is hz ? Suppose xs = [x0, x1, x2] .
Apply the definition of foldr:

 hz = (step x0 (step x1 (step x2 id))) z 

Apply step definition:

 = (\a0 -> (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f a0 x0)) z 

Replace lambda functions:

 = (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (fz x0) = (\a2 -> id (f a2 x2)) (f (fz x0) x1) = id (f (f (fz x0) x1) x2) 

Apply id definition:

 = f (f (fz x0) x1) x2 

Apply definition of foldl:

 = foldl fz [x0, x1, x2] 

Is it the royal road or what?

+1
Jun 24 '14 at 18:46
source share

This might help, I tried to expand in a different way.

 myFoldl (+) 0 [1,2,3] = foldr step id [1,2,3] 0 = foldr step (\a -> id (a+3)) [1,2] 0 = foldr step (\b -> (\a -> id (a+3)) (b+2)) [1] 0 = foldr step (\b -> id ((b+2)+3)) [1] 0 = foldr step (\c -> (\b -> id ((b+2)+3)) (c+1)) [] 0 = foldr step (\c -> id (((c+1)+2)+3)) [] 0 = (\c -> id (((c+1)+2)+3)) 0 = ... 
+1
Jan 21 '18 at 19:38
source share
 foldr step zero (x:xs) = step x (foldr step zero xs) foldr _ zero [] = zero myFold fz xs = foldr step id xs z where step xga = g (fax) myFold (+) 0 [1, 2, 3] = foldr step id [1, 2, 3] 0 -- Expanding foldr function step 1 (foldr step id [2, 3]) 0 step 1 (step 2 (foldr step id [3])) 0 step 1 (step 2 (step 3 (foldr step id []))) 0 -- Expanding step function if it is possible step 1 (step 2 (step 3 id)) 0 step 2 (step 3 id) (0 + 1) step 3 id ((0 + 1) + 2) id (((0 + 1) + 2) + 3) 

Well, at least it helped me. Even this is not entirely correct.

0
Dec 01 '18 at 10:55
source share



All Articles