简体   繁体   中英

Why does foldright work for infinite lists?

I was under the impression that foldright starts from the end of a list and works backwards (this is how I imagined what right-associative means). So I am confused that the following works for infinite lists.

I have a function find :

find :: (a -> Bool) -> List a -> Optional a
find p = foldRight (\c a -> if p c then Full c else a) Empty

Note that the following work:

>> find (const True) infinity
Full 0

I did do some searching and found this post: How do you know when to use fold-left and when to use fold-right?

Unfortunately, the accepted answer is not particularly helpful because the example for right-associative operations is:

A x (B x (C x D))

Which still means it needs to execute the right-most thing first.

I was wondering if anyone can clear this up for me, thanks.

Let's start with a function:

>>> let check x y = if x > 10 then x else y
>>> check 100 5
100
>>> check 0 5
5

check takes two arguments, but might not use its second argument. Since haskell is lazy, this means that the second argument may never be evaluated:

>>> check 20 (error "fire the missles!")
20

This laziness lets us skip a possibly infinite amount of work:

>>> check 30 (sum [1..])
30

Now let's step through foldr check 0 [0..] using equational reasoning:

foldr check 0 [0..]
= check 0 (foldr check 0 [1..]) -- by def'n of foldr
= foldr check 0 [1..] -- by def'n of check
= check 1 (foldr check 0 [2..]) -- by def'n of foldr
= foldr check 0 [2..] -- by def'n of check
-- ...
= foldr check 0 [10..]
= check 10 (foldr check 0 [11..]) -- by def'n of foldr
= foldr check 0 [11..] -- by def'n of check
= check 11 (foldr check 0 [12..]) -- by def'n of foldr
= 11 -- by def'n of check

Note how laziness forces us to evaluate from the top-down, seeing how (and if) the outer-most function call uses its arguments, rather than from the bottom-up (evaluating all arguments before passing them to a function), as strict languages do.

It works because of lazy evaluation. Let's take a really simple example.

import Data.Char (toUpper)

main :: IO ()
main = interact (foldr capitalized []) where
  capitalized :: Char -> String -> String
  capitalized x xs = (toUpper x):xs

Run this program interactively and see what happens. The input is an infinite (or at least indefinite) list of characters read from standard input.

This works because each element of the output list gets produced lazily, when it is needed. So the tail is not produced first: it's only computed if and when it's needed. Until then, it's deferred, and we can use the partial results. The partial result for 'h':xs is 'H':(foldr capitalized [] xs) . The partial result for 'h':'e':'l':'l':'o':',':' ':'w':'o':'r':'l':'d':'!':'\\n':xs is a string we can output before we proceed to the tail xs .

Now see what happens if you try this with foldl .

This works for any data structure that generates a useful prefix. For a reduction operation that produces a single value, and no useful intermediate results, a strict left fold ( Data.List.foldl' ) is usually the better choice.

Your objection proves too much. If it was valid, no infinite lists at all would be possible! An infinite list is constructed using (:) . Its second argument, the tail of the list, is also an infinite list, and would have to be evaluated first. This recursively doesn't get us anywhere.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM