The Wikipedia entry has a good illustration of what foldr
does to a list and how it differs from foldl
.
The list [1,2,3]
is generated by the cons operator (:)
and the empty list []
by this expression tree:
:
/ \
1 :
/ \
2 :
/ \
3 []
foldr f z
replaces the cons operator nodes with f
and the empty list with z
:

By contrast, foldl f z
reorients the expression tree and repositions the zero element at the opposite end of the computation:
Some observations:
Note that foldr (:) []
leaves a list unchanged:
foldr (:) [] [1..10] == [1..10]
This makes sense since we are simply replacing the cons operator with itself and the empty list with the empty list and thus not changing anything.
If we replace the empty list with some other list we should be able to append two lists, e.g.:
foldr (:) [9,8,7] [1,2,3] == [1,2,3,9,8,7]
The cons operator may be thought of a way to add an element to a list:
(:) :: a -> [a] -> [a]
If we define f
as:
f :: Int -> [Int] -> [Int]
f a as = [0] ++ [a] ++ as
then foldr f []
will prepend a 0 before each element of the input list:
foldr f [] [1,2,3] == [0,1,0,2,0,3]
Data.Set
has a function insert
whose signature is structually similar to that of (:)
:
insert :: Ord a => a -> Set a -> Set a
Indeed, we can convert a list to a Set using foldr
import qualified Data.Set as S
foldr S.insert S.empty [1,2,3]
Note how the empty list is replaced with the empty set - S.empty
.
This is the idiomatic approach to building up data structures one element at a time - e.g. hash maps, tries, trees, etc.