我对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)
是什么导致列表理解以这种方式表现?
答案 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是什么。每个列表理解都可以转换为列表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]
这意味着每次设置x
和y
时,您都会创建一个元组(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)
让我们替换xs
,ys
的具体值:
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
)函数。