Skip to content

Commit 0280f45

Browse files
Xingye-Dujingulysses4ever
authored andcommitted
Add an example for foldr on infinite lists
1 parent 2f2747e commit 0280f45

1 file changed

Lines changed: 53 additions & 3 deletions

File tree

source_md/higher-order-functions.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,9 +673,59 @@ It would be `map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs`, but the thing is
673673
If you reverse a list, you can do a right fold on it just like you would have done a left fold and vice versa.
674674
Sometimes you don't even have to do that.
675675
The `sum` function can be implemented pretty much the same with a left and right fold.
676-
One big difference is that right folds work on infinite lists, whereas left ones don't!
677-
To put it plainly, if you take an infinite list at some point and you fold it up from the right, you'll eventually reach the beginning of the list.
678-
However, if you take an infinite list at a point and you try to fold it up from the left, you'll never reach an end!
676+
*One big difference is that right folds work on infinite lists, whereas left ones don't!*
677+
678+
How `foldr` works with infinite lists
679+
680+
Recall the definition of `foldr`:
681+
```{.haskell:hs}
682+
foldr f acc [] = acc
683+
foldr f acc (x:xs) = f x (foldr f acc xs)
684+
```
685+
The key insight is that the recursive call `foldr f acc xs` is passed as an argument to the function `f`. Because Haskell is lazy, if `f` never uses its second argument, the recursive call is never evaluated. This allows `foldr` to terminate on infinite lists, provided `f` is lazy in its right argument.
686+
687+
Example: Checking for a Value in an infinite list
688+
689+
Suppose we want to check if `3` appears in an infinite list `[1..]`. We can use `foldr` like this:
690+
```{.haskell:hs}
691+
containsThree :: [Int] -> Bool
692+
containsThree = foldr (\x rest -> if x == 3 then True else rest) False
693+
```
694+
Let's unfold this step-by-step on the infinite list `[1,2,3,4,...]`:
695+
1. Start: foldr (...) False [1,2,3,4,...]
696+
2. For `x=1`: `if 1 == 3 then True else (foldr (...) False [2,3,4,...])` -> `1 /= 3`, so this becomes `foldr (...) False [2,3,4,...]` (we recurse).
697+
3. For `x=2`: `if 2 == 3 then True else (foldr (...) False [3,4,5,...])` -> `2 /= 3`, so this becomes `foldr (...) False [3,4,5,...]` (we recurse again).
698+
4. For `x=3`: `if 3 == 3 then True else (foldr (...) False [4,5,6,...])` -> `3 == 3` is `True`! *Here, `f` ignores the recursive call (the [4,5,6,...] part) and returns True immediately*.
699+
700+
Why `foldl` Fails with Infinite Lists
701+
702+
Now, contrast this with foldl's definition:
703+
```{.haskell:hs}
704+
foldl f acc [] = acc
705+
foldl f acc (x:xs) = foldl f (f acc x) xs
706+
```
707+
Notice that `foldl` always makes a recursive call first, passing `f acc x` as the new accumulator. This means:
708+
Even if the accumulator becomes True at some point, foldl will still continue recursing down the list.
709+
For an infinite list, this leads to infinite recursion (it never stops).
710+
711+
Example: `foldl` on the Same Problem
712+
713+
If we try to implement containsThree with `foldl`:
714+
```{.haskell:hs}
715+
containsThreeFoldl :: [Int] -> Bool
716+
containsThreeFoldl = foldl (\acc x -> if x == 3 then True else acc) False
717+
```
718+
1. Unfolding on `[1,2,3,4,...]`:
719+
2. Start: `acc = False`, list [1,2,3,4,...].
720+
3. Compute `f False 1` -> `if 1==3 then True else False` -> `False`. Recurse: `foldl (...) False [2,3,4,...]`.
721+
4. Compute `f False 2` -> `False`. Recurse: `foldl (...) False [3,4,5,...]`.
722+
5. Compute `f False 3` -> `True`. But now we must recurse: `foldl (...) True [4,5,6,...]`.
723+
6. Compute `f True 4` -> `True` (since 4 /= 3, it returns the accumulator True). But we recurse again: `foldl (...) True [5,6,7,...]`.
724+
7. This continues forever. Even though we found 3, we never stop.
725+
726+
`foldr` can terminate on infinite lists if the function `f` is lazy in its right argument (i.e., `f` can short-circuit by ignoring the recursive result).
727+
`foldl` always processes the list from left to right and cannot short-circuit in the same way, so it fails on infinite lists.
728+
This is why `foldr` is often more suitable for working with infinite lists. Just remember: *the function `f` must be lazy in its right argument for `foldr` to terminate early*.
679729

680730
**Folds can be used to implement any function where you traverse a list once, element by element, and then return something based on that.
681731
Whenever you want to traverse a list to return something, chances are you want a fold.**

0 commit comments

Comments
 (0)