我在函数式语言中看到了许多关于处理列表和构造函数以在接收到一些附加值(通常在生成函数时不存在)后对其元素执行某些操作的示例,例如:
-
(“懒惰评估”下的最后两个例子)
使用严格的函数式语言(例如 ML/OCaml)暂存列表追加,以避免多次遍历第一个列表
(标题为“分期”的部分)
使用 foldr 将列表与另一个列表进行比较(即生成一个函数以将另一个列表与第一个列表进行比较)
listEq a b = foldr comb null a b where comb x frec [] = False comb x frec (e:es) = x == e && frec es cmp1To10 = listEq [1..10]
在所有这些示例中,作者通常都提到只遍历原始列表一次的好处。但是我不能不让自己思考“当然,不是遍历 N 个元素的列表,而是遍历 N 个评估链,那又怎样?”。我知道它一定有一些好处,有人可以解释一下吗?
编辑:感谢两位的回答。不幸的是,这不是我想知道的。我将尝试澄清我的问题,因此它不会与(更常见的)关于创建中间列表的问题(我已经在各个地方读到过)混淆。也感谢您更正我的帖子格式。
我对您构造要应用于列表的函数的情况感兴趣,在这种情况下您还没有评估结果的必要值(无论是否是列表)。那么你就无法避免生成对每个列表元素的引用(即使不再引用列表结构)。而且您拥有与以前相同的内存访问权限,但您不必解构列表(模式匹配)。
例如,请参阅上述 ML 书中的“暂存”章节。我已经在 ML 和 Racket 中尝试过,更具体地说是“append”的分段版本,它遍历第一个列表并返回一个在尾部插入第二个列表的函数,而无需多次遍历第一个列表。令我惊讶的是,即使考虑到它仍然必须复制列表结构,因为最后一个指针在每种情况下都不同,它也快得多。
以下是 map 的一种变体,它应用于列表后,更改功能时应该更快。由于 Haskell 并不严格,我将不得不强制对listMap [1..100000]
in进行评估cachedList
(或者可能不强制,因为在第一个应用程序之后它仍然应该在内存中)。
listMap = foldr comb (const [])
where comb x rest = \f -> f x : rest f
cachedList = listMap [1..100000]
doubles = cachedList (2*)
squares = cachedList (\x -> x*x)
-- print doubles and squares
-- ...
comb x rest f = ...
我知道在 Haskell 中使用vs并没有什么不同(如果我错了,请纠正我)comb x rest = \f -> ...
,但我选择了这个版本来强调这个想法。
更新:经过一些简单的测试,我在 Haskell 中找不到任何执行时间的差异。那么问题只是关于严格的语言,例如 Scheme(至少是我测试过的 Racket 实现)和 ML。