假设我们有一个预定义的列表pairs
,它是所有对(x,y)的列表,使得x,y∈{1..9}和2x = y。在Haskell中它看起来像:
pairs = [ (x,y) | x <- [1..9], y <- [1..9], 2 * x == y ]
现在,我想使用pairs
来定义一个新列表triplets
,它是所有三元组(x,y,z)的列表,使得x,y,z∈{1。 .9},2x = y,和2y = z。写它的显而易见的方法是:
triplets = [ (x,y,z) | (x,y) <- pairs, (y,z) <- pairs ]
奇怪的是,这不起作用。
现在,我知道技术的原因:(y,z) <- pairs
生成器比(x,y) <- pairs
更紧密地循环,并且它赋予y
的值在进入该循环之前,1}}覆盖y
之前的任何内容。但是你为什么要设计一种语言呢?不要让发电机更直观(并且符合数学惯例),并且不会看到&#39;在其左侧并重用预先分配的值,以便每个变量在每次迭代中都有一个值?我认为这个设计选择必定有一个务实的理由,但我不确定它是什么。
答案 0 :(得分:5)
问题是列表理解只是为列表monad编写符号的另一种方法:
triplets = do
(x, y) <- pairs
(y, z) <- pairs
return (x, y, z)
相反,你会看到你在monadic动作中重新绑定一个名字。虽然这是允许的,但如果您打开-Wall
,则会收到警告
Warning: Defined by not used: `y'
Warning:
The binding for `y' shadows the existing binding bound at module:line:column
相反,首选方法是
triplets = do
(x, y1) <- pairs
(y2, z) <- pairs
guard $ y1 == y2
return (x, y1, z)
或者作为理解
triplets = [(x, y1, z) | (x, y1) <- pairs, (y2, z) <- pairs, y1 == y2]
记住咒语“明确胜过隐性”。你不会因为能够做你想做的事而获得任何效率加成,这显然表达了你的意图,而[(x, y, z) | (x, y) <- pairs, (y, z) <- pairs]
更难理解。
答案 1 :(得分:2)
在操作上,无论如何你能做的最好的事情是以某种方式过滤发电机
[ (x, y, z) | (x, y) <- pairs, (y', z) <- pairs, y == y' ]
或
[ (x, y, z) | (x, y) <- pairs, (_, z) <- filter (\(y', _) -> y == y') pairs ]
考虑这些(直接)如何转换为List monad用法也很有用
-- [ (x, y, z) | (x, y) <- pairs, (y', z) <- pairs, y == y' ]
do (x, y) <- pairs
(y', z) <- pairs
guard (y == y')
return (x, y, z)
使第一个例子中的值阴影更加清晰
-- [ (x, y, z) | (x, y) <- pairs, (y, z) <- pairs ]
do (x, y) <- pairs
(y, z) <- pairs -- shadows!
return (x, y, z)
值得清楚的是,虽然这种直觉一般都有,但列表推导在Haskell报告中明确定义,因此可能与语法重载奇怪地相互作用。
答案 2 :(得分:1)
此设计选择的原因必须是一致性:
g xs = [x | x <- xs, y <- xs]
将是g :: [a] -> [a]
类型,就像现在一样,但是
h xs = [x | x <- xs, x <- xs]
必须是h :: (Eq a) => [a] -> [a]
类型。
如果允许重复模式变量来表示列表推导中的相等性,那么在函数中也必须允许相同,同样存在Eq
约束突然出现的问题。顺便说一下,这样的模式被称为非线性。
比较收到的相等参数也会改变它们的严格性(两者都必须强制)。而且我们不能要求参数只是&#34;相同&#34;因为Haskell的参考透明度,其中&#34;指针平等&#34;未定义(换句话说,在Haskell中必须无法区分x = 1; z = g x x
和x = 1; z = g x 1
。
另一方面,如果确实需要相等,那么添加一个明确的平等守卫就很容易了(正如其他答案所示)。
另见: