在Haskell中,列表理解的内部工作原理是什么?

时间:2015-05-13 01:09:14

标签: list haskell

我对Haskell很新,并且在这里看到这篇文章:Cartesian product of 2 lists in Haskell

在答案中有这段代码:

cartProd xs ys = [(x,y) | x <- xs, y <- ys]

使用这两个列表:

xs = [1,2,3]
ys = [4,5,6]

会产生

[(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]

如果我没有看到这个结果,我会认为它只会返回

[(1,4),(2,5),(3,6)]

因为它会同时遍历两个列表。

但是现在它 - 对于我更熟悉的编程语言 - 看起来像用于遍历矩阵的双循环:

for (int x = 1; x < 4; x++)
    for(int y = 4; y < 7; y++)
        //make tuple (x,y)

是什么导致列表理解以这种方式表现?

2 个答案:

答案 0 :(得分:8)

这个introduction解释了列表理解的语法。基本上可以说每个x <- list意味着一个额外的嵌套&#34; for&#34; -loop来生成元组,并且每个谓词都只是被测试。因此表达式:

[(x,y) | x <- xs, even x, y <- ys, even 3*y-div x 2]

将用命令式语言翻译为:

for (var x : xs) {
    if(even(x)) {
    for(var y : ys) {
        if(even(3*y-x/2)) {
            yield (x,y)
        }
    }
}

yield是一个有时与协同例程一起使用的关键字。此外,对于yield,评估是懒惰的。例如,这可以生成所有甚至整数:

[x|x <-[2..],even x]

列出monads

为了从根本上理解列表理解,需要知道Monads是什么。每个列表理解都可以转换为列表monad 。例如,您的示例被翻译为:

do x <- xs
   return
      (do y <- ys
          return (x,y))

这又是语法糖:

xs >>= (\x -> (ys >>= \y -> return (x,y)))

monad是函数式编程中的一个重要概念(可能还有一个更好的读取维基百科页面),因为它有点难以掌握。有时会说 monads就像墨西哥卷饼,......

一旦你或多或少地理解了一个monad:monad是一个带有return语句和>>通道语句的类型类。现在内部的return语句很简单:

return x = [x]

这意味着每次设置xy时,您都会创建一个元组(x,y)并将其作为单例列表返回:[(x,y)]。现在&#34;绑定&#34;运营商>>=需要&#34;粘合&#34; ys\y -> return (x,y)在一起。这是通过将其实现为:

来完成的
(>>=) xs f = concat $ map f xs

换句话说,您执行映射并连接映射的结果。

现在,如果考虑未经过表达的表达式的第二部分:

ys >>= \y -> return (x,y)

这意味着对于给定的x(我们现在抽象了),我们会将ys中的每个元素映射到元组(x,y)并返回它。因此,我们将生成一个列表列表,每个列表都是包含元组的单例。像(如果ys=[1,2]):

[[(x,1)],[(x,2)]]

现在>>=还会concat进入:

\x -> [(x,1),(x,2)]

到目前为止,我们已经将x抽象出去了(假设它是一个)。但现在我们可以采用该表达式的第一部分:

xs >>= \x -> [(x,1),(x,2)]

如果xs=[3,5],则表示我们会再次创建列表:

[[(3,1),(3,2)],[(5,1),(5,2)]]

并且在concat之后:

[(3,1),(3,2),(5,1),(5,2)]

我们期望的是:

[(x,y)|x<-[3,5],y<-[1,2]]

答案 1 :(得分:6)

从Haskell报告中引用,列表推导评估如下:

[ e | True ]   = [e]
[ e | q ]      = [ e | q, True ]
[ e | b,  Q  ] = if b then [ e | Q ] else []
[ e | p <- l,  Q ] = let ok p = [ e | Q ]
                         ok _ = []
                     in concatMap ok  l
[ e | let decls,  Q ] = let decls in [ e | Q ]

在您的情况下,相关部分是,因为模式p只是变量x

[ e | x <- l, Q ] = concatMap (\x -> [ e | Q ]) l

更具体地说,理解[(x,y) | x <- xs, y <- ys]被翻译为

concatMap (\x -> [(x,y) | y <- ys]) xs

根据concatMap

的定义
concat (map (\x -> [(x,y) | y <- ys]) xs)

让我们替换xsys的具体值:

concat (map (\x -> [(x,y) | y <- [4,5,6]]) [1,2,3])

应用map

concat [ [(1,y) | y <- [4,5,6]] 
       , [(2,y) | y <- [4,5,6]] 
       , [(3,y) | y <- [4,5,6]] ]

评估内部列表理解:(这些可以使用上面的法律再次翻译,但我会简短地说明)

concat [ [(1,4),(1,5),(1,6)]
       , [(2,4),(2,5),(2,6)]
       , [(3,4),(3,5),(3,6)] ]

通过连接上面的列表,我们得到了结果。

请注意,GHC还实现了所谓的并行列表推导作为Haskell扩展,它可以按预期运行:

> :set -XParallelListComp
> [(x,y)| x<-[1,2,3] | y <-[4,5,6]]
[(1,4),(2,5),(3,6)]

在内部,他们使用zip(或更确切地说,zipWith)函数。