我正在研究Haskell中的一个类,我们正在使用列表推导来实现等价关系,这有点令人困惑。例如,我无法理解在这种情况下究竟发生了什么。
exmp2 :: Reln -> [(Int, Int)]
exmp2 rs = [ (j, i) | (i,j) <- rs, (j,i) <- rs ]
VS
exmpl1 :: Reln -> [(Int, Int)]
exmpl1 rs = [ (j, i) | (i,j) <- rs ]
如果我使用上述......
exmpl1 [(1, 2), (2, 7), (1,9)]
输出
[(2, 1), (7,2), (9,1)]
exmpl2 [(1, 2), (2, 7), (1,9)]
输出
[(1,2), (2,7), (1,9), (1,2), (2,7), (1,9), (1,2), (2,7), (1,9)]
我已经阅读了列表推导教程等,但我不能为我的生活弄清楚为什么exmpl2
确切地输出了什么。感谢
答案 0 :(得分:3)
我觉得从一个更简单的例子中接近它可以更容易理解。
λ let chars = ['a', 'b', 'c']
λ let bools = [True, False]
λ [(c, b) | c <- chars, b <- bools]
[('a',True),('a',False),('b',True),('b',False),('c',True),('c',False)]
应该很清楚,我们已经制作了从c
中获取一个chars
和从b
中获取一个bools
的所有可能组合。
下一步是了解我可以使用(c, b)
放置的任何表达式。该部分对结果列表中的项目的数量具有零影响;无论我放在那里,我仍然会为来自c
和b
的{{1}}和chars
的每个组合提供一个项目。我甚至不需要实际使用bools
和c
(尽管显然你通常会使用实际代码)。
举个例子:
b
对于λ [ 7 | c <- chars, b <- bools]
[7,7,7,7,7,7]
中的c
和chars
中的b
的每个组合,我都会生成bools
。这是我列表中的六个7
项目。
我也可以只引用7
和c
中的一个,但它仍然不会改变我提出的项目数量:
b
我使用的唯一值是来自λ [ b | c <- chars, b <- bools ]
[True,False,True,False,True,False]
的{{1}}和True
,但确定了列表的形状(有六个项目)通过“False
和bools
”逻辑的每个组合。
现在我可以提取的一个稍微邪恶的技巧(在你的chars
中使用)是因为我从不使用bools
因为我用exmp2
提取的变量名称无关紧要{{ 1}}。因为当我从c
中提取时,我以后绑定chars
,我可以在那里使用b
。 bools
阴影中的b
的第二个绑定第一个绑定;它实际上是一个完全独立的变量,但由于它们具有相同的名称,我现在只能引用它们中的一个。由于我只使用其中一个,这不会影响我正在尝试做的事情(但它确实使代码更难理解,因为你必须知道更多来确定哪个b
绑定是影响我的列表中的值的那个:)
b <- bools
现在我们来看看你的例子:
b
这是来自λ [ b | b <- chars, b <- bools ]
[True,False,True,False,True,False]
的{{1}}和exmp2 :: Reln -> [(Int, Int)]
exmp2 rs = [ (j, i) | (i,j) <- rs, (j,i) <- rs ]
的{{1}}的每个组合。暂时忽略(i, j)
和rs
;我们通过从同一个列表中选择两件事来获取我们得到的所有组合。因此,我们最终会得到输出列表中(j, i)
中项目数的平方。
第一个rs
案例绑定变量i
和j
。但是第二种情况再次绑定了相同的变量,所以它们完全无关紧要。我们可以把它看成是:
rs
因此,对于(i, j) <- rs
中我们不关心的一对的每种可能组合,以及来自i
的另一对j
,会生成exmp2 rs = [ (j, i) | _ <- rs, (j,i) <- rs ]
对。< / p>
这就是为什么在rs
中你得到的输出包含3个序列(j, i)
的副本;第一对rs
一次,第二对(j, i)
一次,第三对exmpl2 [(1, 2), (2, 7), (1,9)]
一次。
答案 1 :(得分:2)
列表 - 理解语法也可以使用&#34; do&#34;来表达。符号,不那么神秘,也不易解释。
,例如,以下理解:
[ (j, i) | (i,j) <- rs, (j,i) <- rs ]
等同于以下表达式:
do
(i, j) <- rs
(j, i) <- rs
return (j, i)
所以这里发生的是你首先从rs
monad(在本例中为List)和desctructure中提取一个值,并将其分配给i
和j
变量。然后再次提取值并以相反的顺序将其分配给相同的变量。因此,您将影响第一个作业。之后,您只需为输出monad(List)生成值。
由于第一个赋值被遮蔽,我们可以简单地绕过它,得到以下等价表达式:
do
_ <- rs
(j, i) <- rs
return (j, i)
甚至:
do
rs
(j, i) <- rs
return (j, i)
定义List monad,以便您浏览所有提取的所有可能组合。
在这种情况下,你会提取两次。结果,您将获得rs
输入列表的双遍历。
用命令式语言看起来像这样:
function exmpl2(rs) {
var results = [];
for (a in rs) {
for (b in rs) {
results.push(b);
}
}
return results;
}