[英]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
。
在您的情况下,在第一个定义中, x
在cp 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.