Left and Right Folding over an Infinite list

前端 未结 5 1544
北荒
北荒 2020-12-07 14:03

I have issues with the following passage from Learn You A Haskell (Great book imo, not dissing it):

One big difference is that right folds work on i

5条回答
  •  谎友^
    谎友^ (楼主)
    2020-12-07 14:44

    Remember in Haskell you can use infinite lists because of lazy evaluation. So, head [1..] is just 1, and head $ map (+1) [1..] is 2, even though `[1..] is infinitely long. If you dont get that, stop and play with it for a while. If you do get that, read on...

    I think part of your confusion is that the foldl and foldr always start at one side or the other, hence you dont need to give a length.

    foldr has a very simple definition

     foldr _ z [] = z
     foldr f z (x:xs) = f x $ foldr f z xs
    

    why might this terminate on infinite lists, well try

     dumbFunc :: a -> b -> String
     dumbFunc _ _ = "always returns the same string"
     testFold = foldr dumbFunc 0 [1..]
    

    here we pass into foldr a "" (since the value doesn't matter) and the infinite list of natural numbers. Does this terminate? Yes.

    The reason it terminates is because Haskell's evaluation is equivalent to lazy term rewriting.

    So

     testFold = foldr dumbFunc "" [1..]
    

    becomes (to allow pattern matching)

     testFold = foldr dumbFunc "" (1:[2..])
    

    which is the same as (from our definition of fold)

     testFold = dumbFunc 1 $ foldr dumbFunc "" [2..]
    

    now by the definition of dumbFunc we can conclude

     testFold = "always returns the same string"
    

    This is more interesting when we have functions that do something, but are sometimes lazy. For example

    foldr (||) False 
    

    is used to find if a list contains any True elements. We can use this to define the higher order functionn any which returns True if and only if the passed in function is true for some element of the list

    any :: (a -> Bool) -> [a] -> Bool
    any f = (foldr (||) False) . (map f)
    

    The nice thing about lazy evaluation, is that this will stop when it encounters the first element e such that f e == True

    On the other hand, this isn't true of foldl. Why? Well a really simple foldl looks like

    foldl f z []     = z                  
    foldl f z (x:xs) = foldl f (f z x) xs
    

    Now, what would have happened if we tried our example above

    testFold' = foldl dumbFunc "" [1..]
    testFold' = foldl dumbFunc "" (1:[2..])
    

    this now becomes:

    testFold' = foldl dumbFunc (dumbFunc "" 1) [2..]
    

    so

    testFold' = foldl dumbFunc (dumbFunc (dumbFunc "" 1) 2) [3..]
    testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) [4..]
    testFold' = foldl dumbFunc (dumbFunc (dumbFunc (dumbFunc (dumbFunc "" 1) 2) 3) 4) [5..]
    

    and so on and so on. We can never get anywhere, because Haskell always evaluates the outermost function first (that is lazy evaluation in a nutshell).

    One cool consequence of this is that you can implement foldl out of foldr but not vice versa. This means that in some profound way foldr is the most fundamental of all the higher order string functions, since it is the one we use to implement almost all the others. You still might want to use a foldl sometimes, because you can implement foldl tail recursively, and get some performance gain from that.

提交回复
热议问题