用更大的视角补充其他答案:对于惰性列表,foldl'
在返回列表的函数中使用通常是一个坏主意。 foldl'
当您将列表缩减为严格(非惰性)标量值(例如,对列表求和)时,它通常很有用。但是当你建立一个列表作为结果时,foldr
通常会更好,因为懒惰;构造函数是惰性的:
,因此在实际需要之前不会计算列表的尾部。
在你的情况下:
newList_foldr lst = foldr (\x acc -> x*2 : acc) [] lst
这实际上与map (*2)
:
newList_foldr lst = map (*2) lst
map f lst = foldr (\x acc -> f x : acc) [] lst
评估(使用第一个map
-less 定义):
newList_foldr [1..10]
= foldr (\x acc -> x*2 : acc) [] [1..10]
= foldr (\x acc -> x*2 : acc) [] (1:[2..10])
= 1*2 : foldr (\x rest -> f x : acc) [] [2..10]
这大约是 Haskell 将评估何时newList [1..10]
被强制。如果这个结果的消费者需要它,它只会进一步评估 - 并且只需要满足消费者的需求。例如:
firstElem [] = Nothing
firstElem (x:_) = Just x
firstElem (newList_foldr [1..10])
-- firstElem only needs to evaluate newList [1..10] enough to determine
-- which of its subcases applies—empty list or pair.
= firstElem (foldr (\x acc -> x*2 : acc) [] [1..10])
= firstElem (foldr (\x acc -> x*2 : acc) [] (1:[2..10]))
= firstElem (1*2 : foldr (\x rest -> f x : acc) [] [2..10])
-- firstElem doesn't need the tail, so it's never computed!
= Just (1*2)
这也意味着foldr
-basednewList
也可以与无限列表一起使用:
newList_foldr [1..] = [2,4..]
firstElem (newList_foldr [1..]) = 2
foldl'
另一方面,如果使用,则必须始终计算整个列表,这也意味着您不能处理无限列表:
firstElem (newList_good [1..]) -- doesn't terminate
firstElem (newList_good [1..10])
= firstElem (foldl' (\acc x -> x*2 : acc) [] [1..10])
= firstElem (foldl' (\acc x -> x*2 : acc) [] (1:[2..10]))
= firstElem (foldl' (\acc x -> x*2 : acc) [2] [2..10])
-- we can't short circuit here because the [2] is "inside" the foldl', so
-- firstElem can't see it
= firstElem (foldl' (\acc x -> x*2 : acc) [2] (2:[3..10]))
= firstElem (foldl' (\acc x -> x*2 : acc) [4,2] [3..10])
...
= firstElem (foldl' (\acc x -> x*2 : acc) [18,16,14,12,10,8,6,4,2] (10:[]))
= firstElem (foldl' (\acc x -> x*2 : acc) [20,18,16,14,12,10,8,6,4,2] [])
= firstElem [20,18,16,14,12,10,8,6,4,2]
= firstElem (20:[18,16,14,12,10,8,6,4,2])
= Just 20
foldr
基于 - 的算法需要 4 个步骤来计算firstElem_foldr (newList [1..10])
,而基于-的算法需要foldl'
大约 21 个步骤。更糟糕的是,这 4 个步骤是固定成本,而 21 与输入列表的长度成正比——<code>firstElem (newList_good [1..150000]) 需要 300,001 步,而firstElem (newList_foldr [1..150000]
需要 5 步,firstElem (newList_foldr [1..]
就像那件事。
另请注意,firstElem (newList_foldr [1.10])
在恒定空间和恒定时间中运行(它必须;您需要比恒定时间更多的时间来分配比恒定空间更多的空间)。严格foldl
语言的突出性——“foldl
是尾递归的并且在恒定空间中运行,foldr
不是尾递归并且在线性空间中运行或更糟”——在 Haskell 中是不正确的。