我在函数式语言中看到过很多处理列表并构建一个函数来处理其元素的示例,以便在接收到某些额外值(通常不在生成函数时出现)后执行某些操作,例如:
-
(“惰性求值”下的最后两个示例)
在严格的函数式语言中(如ML / OCaml)分阶段进行列表附加,以避免多次遍历第一个列表
(标题为“Staging”的部分)
使用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中的它,更具体地说是“附加”的暂停版本,它遍历第一个列表并返回一个函数,以将第二个列表插入到尾部,而无需多次遍历第一个列表。令我惊讶的是,即使考虑到它仍然必须复制列表结构(因为每种情况下最后一个指针都不同),它仍然要快得多。
以下是map的变体,当更改函数时,应用于列表后速度应更快。由于Haskell不是严格的,我需要在cachedList
中评估listMap [1..100000]
的结果(或者也许不需要,在第一次应用之后,它仍应该在内存中)。
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
-- ...
我知道在 Haskell 中,使用 comb x rest f = ...
和 comb x rest = \f -> ...
好像没有区别(如果我错了,请纠正我),但我选择这个版本来强调这个想法。
更新:经过一些简单的测试,我没有发现在 Haskell 中执行时间上的差异。那么问题仅适用于严格语言,比如 Scheme(至少是我测试过的 Racket 实现)和 ML。
listEq a b = foldr comb null b a
而不是listEq a b = foldr comb null a b
?这段代码来自哪里? - Will Ness