Search code examples
haskellfold

How does fold works for empty list?


When we fold a list with one or more elements inside as done below:

foldr (+) 0 [1,2,3]

We get:

foldr (+) 0 (1 : 2 : 3 : [])

foldr (+) 1 + (2 +(3 + 0)) // 6

Now when the list is empty:

 foldr (+) 0 []

Result: foldr (+) 0 ([])

Since (+) is binary operator, it needs two arguments to complete but here we end up (+) 0. How does it result in 0 and not throwing error of partially applied function.


Solution

  • Short answer: you get the initial value z.

    If you give foldl or foldr an empty list, then it returns the initial value. foldr :: (a -> b -> b) -> b -> t a -> b works like:

    foldr f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z)...)
    

    So since there are no x1, ..., xn the function is never applied, and z is returned.

    We can also inspect the source code:

    foldr            :: (a -> b -> b) -> b -> [a] -> b
    -- foldr _ z []     =  z
    -- foldr f z (x:xs) =  f x (foldr f z xs)
    {-# INLINE [0] foldr #-}
    -- Inline only in the final stage, after the foldr/cons rule has had a chance
    -- Also note that we inline it when it has *two* parameters, which are the
    -- ones we are keen about specialising!
    foldr k z = go
              where
                go []     = z
                go (y:ys) = y `k` go ys
    

    So if we give foldr an empty list, then go will immediately work on that empty list, and return z, the initial value.

    A cleaner syntax (and a bit less efficient, as is written in the comment of the function) would thus be:

    foldr :: (a -> b -> b) -> b -> [a] -> b
    foldr _ z [] = z
    foldr f z (x:xs) = f x (foldr f z xs)
    

    Note that - depending on the implementation of f - it is possible to foldr on infinite lists: if at some point f only looks at the initial value, and then returns a value, then the recursive part can be dropped.