【发布时间】:2023-03-07 08:52:01
【问题描述】:
我在函数式语言中看到了很多关于处理列表和构造函数以在接收到一些附加值(通常在生成函数时不存在)后对其元素执行某些操作的示例,例如:
-
Calculating the difference between each element and the average
(“懒惰评估”下的最后两个例子)
-
以严格的函数式语言(例如 ML/OCaml、to avoid traversing the first list more than once)暂存列表追加
(标题为“暂存”的部分)
-
使用 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 并不严格,我将不得不在 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?哪里来的? -
关于 staging - 我认为它允许部分预编译。这样的预编译函数体现了一些已经完成的工作,提前。特别是,列表的遍历只进行一次,所有对单个元素的引用都从列表中提取出来并存储在一个已编译的函数中,以备使用。不遍历编译函数,只遍历解释函数。
-
关于“a b”与“b a”你是对的,它应该是相反的。实际上它们可以从两边删除,我保留它们的原因是因为 ghci 在没有它们的情况下会产生错误(使用“let listEQ = ...”时)。如果它们的顺序相反,我正在搜索的部分评估将不会发生。
-
“...不会发生”——这正是我建议的原因。不过,您忘记在上次编辑中更改它。 :)
-
一个相关的 Haskell 链接:haskell.org/haskellwiki/Runtime_compilation,提到了here。虽然,在 Haskell 中,我们无法明确区分柯里化声明和实际登台(编译)。通常,如果声明了 named 实体,它将被编译为单独的内存驻留实体;但这是一个编译器的东西(又名实现细节)。
标签: haskell functional-programming ml