为什么生成器会在Haskell列表理解中覆盖变量值?

时间:2014-03-19 14:36:51

标签: haskell list-comprehension

假设我们有一个预定义的列表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;在其左侧并重用预先分配的值,以便每个变量在每次迭代中都有一个值?我认为这个设计选择必定有一个务实的理由,但我不确定它是什么。

3 个答案:

答案 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 xx = 1; z = g x 1

另一方面,如果确实需要相等,那么添加一个明确的平等守卫就很容易了(正如其他答案所示)。

另见: