Consider list concatenation implemented as a fold left, that is, foldl (++) []
.
What is the complexity of this implementation in a lazy-evaluated language, such as Haskell?
I understand that in a strict language the performance is quadratic in the total number of elements, but what happens when laziness is involved?
I've tried to evaluate by hand the expression ([1,2,3] ++ [4,5,6]) ++ [7,8,9]
(which corresponds to foldl (++) [] [[1,2,3], [4,5,6], [7,8,9]]
)
and it seems that we traverse only once each element, but I'm not sure whether my reasoning is correct:
([1,2,3] ++ [4,5,6]) ++ [7,8,9]
= { rewrite expression in prefix notation }
(++) ((++) [1,2,3] [4,5,6]) [7,8,9]
= { the first (++) operation needs to pattern match on its first argument; so it evaluates the first argument, which pattern matches on [1,2,3] }
(++) (case [1,2,3] of {[] -> [4,5,6]; x:xs' -> x:(++) xs' [4,5,6]}) [7,8,9]
= { x = 1, xs' = [2,3] }
(++) (1:(++) [2,3] [4,5,6]) [7,8,9]
= { the first (++) operation can now pattern match on its first argument }
1:([2,3] ++ [4,5,6]) ++ [7,8,9]
I have assumed the following implementation of (++)
:
(++) :: [a] -> [a] -> [a]
xs ++ ys = case xs of [] -> ys
(x:xs') -> x : (xs' ++ ys)