How does foldr work?

后端 未结 10 2044
别跟我提以往
别跟我提以往 2020-12-04 06:37

Can anybody explain how does foldr work?

Take these examples:

Prelude> foldr (-) 54 [10, 11]
53
Prelude> foldr (\\x y -> (x+y)/2) 54 [12, 4,         


        
相关标签:
10条回答
  • 2020-12-04 07:32

    I think that implementing map, foldl and foldr in a simple fashion helps explain how they work. Worked examples also aid in our understanding.

      myMap f [] = []
      myMap f (x:xs) = f x : myMap f xs
    
      myFoldL f i [] = i
      myFoldL f i (x:xs) = myFoldL f (f i x) xs
    
      > tail [1,2,3,4] ==> [2,3,4]
      > last [1,2,3,4] ==> 4
      > head [1,2,3,4] ==> 1
      > init [1,2,3,4] ==> [1,2,3]
    
      -- where f is a function,
      --  acc is an accumulator which is given initially
      --  l is a list.
      --
      myFoldR' f acc [] = acc
      myFoldR' f acc l = myFoldR' f (f acc (last l)) (init l)
    
      myFoldR f z []     = z
      myFoldR f z (x:xs) = f x (myFoldR f z xs)
    
      > map (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
      > myMap (\x -> x/2) [12,4,10,6] ==> [6.0,2.0,5.0,3.0]
    
      > foldl (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
      > myFoldL (\x y -> (x+y)/2) 54 [12, 4, 10, 6] ==> 10.125
    
        foldl from above: Starting accumulator = 54
          (12  + 54) / 2 = 33
          (4 + 33) / 2 = 18.5
          (10  + 18.5) / 2 = 14.25
          (6 + 14.25) / 2 = 10.125`
    
     > foldr (++) "5" ["1", "2", "3", "4"] ==> "12345"
    
     > foldl (++) "5" ["1", "2", "3", "4"] ==> “51234"
    
     > foldr (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
     > myFoldR' (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
     > myFoldR (\x y -> (x+y)/2) 54 [12,4,10,6] ==> 12
    
        foldr from above: Starting accumulator = 54
            (6  + 54) / 2 = 30
            (10 + 30) / 2 = 20
            (4  + 20) / 2 = 12
            (12 + 12) / 2 = 12
    
    0 讨论(0)
  • 2020-12-04 07:34

    Careful readings of -- and comparisons between -- the other answers provided here should already make this clear, but it's worth noting that the accepted answer might be a bit misleading to beginners. As other commenters have noted, the computation foldr performs in Haskell does not "begin at the right hand end of the list"; otherwise, foldr could never work on infinite lists (which it does in Haskell, under the right conditions).

    The source code for Haskell's foldr function should make this clear:

    foldr k z = go
              where
                go []     = z
                go (y:ys) = y `k` go ys
    

    Each recursive computation combines the left-most atomic list item with a recursive computation over the tail of the list, viz:

    a\[1\] `f` (a[2] `f` (a[3] `f` ... (a[n-1] `f` a[n])  ...))
    

    where a[n] is the initial accumulator.

    Because reduction is done "lazily in Haskell," it actually begins at the left. This is what we mean by "lazy evaluation," and it's famously a distinguishing feature of Haskell. And it's important in understanding the operation of Haskell's foldr; because, in fact, foldr builds up and reduces computations recursively from the left, binary operators that can short-circuit have an opportunity to, allowing infinite lists to be reduced by foldr under appropriate circumstances.

    It will lead to far less confusion to beginners to say rather that the r ("right") and l ("left") in foldr and foldl refer to right associativity and left associativity and either leave it at that, or try and explain the implications of Haskell's lazy evaluation mechanism.

    To work through your examples, following the foldr source code, we build up the following expression:

    Prelude> foldr (-) 54 [10, 11]
    
    ->
    
    10 - [11 - 54] = 53
    

    And again:

    foldr (\x y -> (x + y) / 2) 54 [12, 4, 10, 6]
    
    ->
    
    (12 + (4 + (10 + (6 + 54) / 2) / 2) / 2) / 2 = 12
    
    0 讨论(0)
  • 2020-12-04 07:40

    foldr begins at the right-hand end of the list and combines each list entry with the accumulator value using the function you give it. The result is the final value of the accumulator after "folding" in all the list elements. Its type is:

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

    and from this you can see that the list element (of type a) is the first argument to the given function, and the accumulator (of type b) is the second.

    For your first example:

    Starting accumulator = 54
    11 -   54  = -43
    10 - (-43) =  53
    
            ^  Result from the previous line
    
     ^ Next list item
    

    So the answer you got was 53.

    The second example:

    Starting accumulator = 54
    (6  + 54) / 2 = 30
    (10 + 30) / 2 = 20
    (4  + 20) / 2 = 12
    (12 + 12) / 2 = 12
    

    So the result is 12.

    Edit: I meant to add, that's for finite lists. foldr can also work on infinite lists but it's best to get your head around the finite case first, I think.

    0 讨论(0)
  • 2020-12-04 07:42

    The easiest way to understand foldr is to rewrite the list you're folding over without the sugar.

    [1,2,3,4,5] => 1:(2:(3:(4:(5:[]))))
    

    now what foldr f x does is that it replaces each : with f in infix form and [] with x and evaluates the result.

    For example:

    sum [1,2,3] = foldr (+) 0 [1,2,3]
    
    [1,2,3] === 1:(2:(3:[]))
    

    so

    sum [1,2,3] === 1+(2+(3+0)) = 6
    
    0 讨论(0)
提交回复
热议问题