Search code examples
haskellfunctional-programminglanguage-agnosticfold

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.


Solution

  • 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.