Haskell 列表理解中的where子句
以下两个公式的区别是什么Haskell 列表理解中的where子句,haskell,list-comprehension,Haskell,List Comprehension,以下两个公式的区别是什么 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[]=[]] cp(xs:xss)=[x:ys | x好吧
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[]=[]]
cp(xs:xss)=[x:ys | x好吧,一个简单的去糖过程应该是:
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
的值,因为如果表达式计算昂贵,编译器将通常情况下,在内联它时会非常犹豫(将第二个版本转换回第一个版本)。当然,这两个定义在表示相同值的意义上是等价的
在操作上,它们在按需调用评估下的共享行为上有所不同。jcast已经解释了原因,但我想添加一个不需要显式取消列表理解的快捷方式。规则是:在语法上可能依赖于变量x
的任何表达式都将在每次调用时重新计算变量x
绑定到某个值的时间,即使表达式实际上不依赖于x
在您的情况下,在第一个定义中,x
在cp-xss
出现的位置处于作用域内,因此cp-xss
将针对xs
的每个元素x
重新计算一次。在第二个定义中,cp-xss
出现在x
的作用域之外,因此只计算一次
然后适用通常的免责声明,即:
- 编译器不需要遵循按需调用求值的操作语义,只需要遵循指称语义。因此,它可能会比基于上述规则的预期计算次数更少(向外浮动)或更多(向内浮动)
- 一般来说,共享越多越好并不正确。例如,在这种情况下,这可能并不更好,因为
cp xss
的大小增长速度与最初计算它所需的工作量一样快。在这种情况下,从内存读回值的成本可能超过重新计算值的成本(由于缓存层次结构和GC)
相关: