繁体   English   中英

列表推导中的条款

[英]where clauses in list comprehensions

以下两个公式有什么区别?

cp [] = [[]]
cp (xs:xss) = [x:ys | x <- xs, ys <- cp xss]
----------------------------------------------
cp [] = [[]]
cp (xs:xss) = [x:ys | x <- xs, ys <- yss]
              where yss = cp xss

样本输出: cp [[1,2,3],[4,5]] => [[1,4],[1,5],[2,4],[2,5],[3,4],[3,5]]

根据与Haskell的功能性思考 (p.92),第二个版本是“更有效的定义... [保证cp xss只计算一次”,尽管作者从未解释过为什么。 我原以为他们是等同的。

当然,这两个定义在它们表示相同值的意义上是等价的。

在操作上,它们在按需调用评估中的共享行为方面存在差异。 jcast已经解释了为什么,但是我想添加一个不需要明确地去除列表理解的快捷方式。 规则是:每次将变量x绑定到某个值时,语法上处于可依赖于变量x的位置的任何表达式都将被重新计算,即使该表达式实际上不依赖于x

在您的情况下,在第一个定义中, xcp xss出现的位置的范围内,因此将对xs每个元素x重新计算cp xss 在第二个定义中, cp xss出现在x的范围之外,因此它只会被计算一次。

然后通常的免责声明适用,即:

  • 编译器不需要遵循按需调用评估的操作语义,只需遵循指称语义。 因此,根据上述规则,它可能会比您预期的更少次数(浮动)或更多次(浮动)。

  • 一般而言,更多共享更好是不正确的。 在这种情况下,例如,它可能不会更好,因为cp xss的大小增长速度与首先计算它的工作量一样快。 在这种情况下,从内存中读取值的成本可能超过重新计算值的成本(由于缓存层次结构和GC)。

好吧,一个天真的脱糖将是:

cp [] = [[]]
cp (xs:xss) = concatMap (\x -> concatMap (\ ys -> [ x:ys ]) (cp xss)) xs
----------------------------------------------
cp [] = [[]]
cp (xs:xss) = let yss = cp xss in concatMap (\x -> concatMap (\ ys -> [ x:ys ]) yss) xs

如您所见,在第一个版本中,调用cp xss位于lambda中。 除非优化器移动它,否则每次调用函数\\x -> concatMap (\\ ys -> [ x:ys ]) (cp xss)都会重新评估它。 通过浮动它,我们避免重新计算。

同时,GHC确实有一个优化传递来从这样的循环中浮动昂贵的计算,因此它可以自动将第一个版本转换为第二个版本。 你的书说第二个版本'保证'只计算一次cp xss的值,因为如果表达式的计算成本很高,编译器通常会非常犹豫地内联它(将第二个版本转换回第一个版本)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM