需要基本 do 块语法的解释
Need explanation for basic do block syntax
在ghci中,我写道:
let x = do
i <- [1..5]
j <- [2..4]
return i
预期结果:
[1,2,3,4,5]
实际结果:
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
我不明白输出背后的逻辑。我认为原因可能与 monad 有关,但我对函数式编程很陌生,我希望有人能解释一下。
我也试过List-comprehension中的等价形式,结果是一样的,这意味着我在这里误解了一些基本的东西。
这是因为 do 机制不关心(幸运的是)最内层的代码是否实际引用(某些)循环变量。
看到你总是得到 3*5=15 个值,不管最里面的代码是什么:
λ>
λ> xs1 = do { i <- [1..5] ; j <- [2..4] ; return i }
λ> xs1
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
λ>
λ> xs2 = do { i <- [1..5] ; j <- [2..4] ; return 9 }
λ> xs2
[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9]
λ>
λ> xs3 = do { i <- [1..5] ; j <- [2..4] ; return (i,j) }
λ> xs3
[(1,2),(1,3),(1,4),(2,2),(2,3),(2,4),(3,2),(3,3),(3,4),(4,2),(4,3),(4,4),(5,2),(5,3),(5,4)]
λ>
λ> length xs1
15
λ> length xs2
15
λ> length xs3
15
λ>
据我所知,这是完全标准的行为,Haskell 与 C、C++、Fortran、Python ...
共享
C++ 等效示例:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> vi{1,2,3,4,5};
std::vector<int> vj{2,3,4};
for (int i: vi)
for (int j: vj)
std::cout << i << ", ";
std::cout << std::endl;
return EXIT_SUCCESS;
}
C++ 输出:
$ ./a.out
1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5,
$
I've also tried the equavalent form in List-comprehension and the result is the same
好主意。碰巧对于列表,do
表示法与列表理解完全相同。 (实际上,a syntactic extension 允许您对任何 monad 使用 list-comprehension 表示法,就像您可以对任何 monad 使用 do
表示法一样。)
所以,您问的是为什么 [a | a<-[0,1], b<-[2,3]]
给出 [0,0,1,1]
而不是 [0,1]
。这看起来令人惊讶的方式是,如果您将列表推导视为 集合推导,就像您在数学中发现的那样。但是列表不是集合,尽管 Haskellers 确实经常使用列表作为集合的临时 stand-in。如果列表推导充当集合推导,则
[x | x <- [0,1,0]]
也应该只产生 [0,1]
作为结果(或者至少,它应该产生 与 [x|x<-[0,1]]
相同的 结果)。
一般来说,这种 weeding-out-duplicates 需要进行相等性检查,如果你想让它更有效率,也可以使用排序或散列方法。列表不会做任何这样的事情,所以如果你想要 set-like 行为,你应该使用 set-implementing 数据结构。 Set
and HashSet
是最常见的。
在ghci中,我写道:
let x = do
i <- [1..5]
j <- [2..4]
return i
预期结果:
[1,2,3,4,5]
实际结果:
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
我不明白输出背后的逻辑。我认为原因可能与 monad 有关,但我对函数式编程很陌生,我希望有人能解释一下。
我也试过List-comprehension中的等价形式,结果是一样的,这意味着我在这里误解了一些基本的东西。
这是因为 do 机制不关心(幸运的是)最内层的代码是否实际引用(某些)循环变量。
看到你总是得到 3*5=15 个值,不管最里面的代码是什么:
λ>
λ> xs1 = do { i <- [1..5] ; j <- [2..4] ; return i }
λ> xs1
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
λ>
λ> xs2 = do { i <- [1..5] ; j <- [2..4] ; return 9 }
λ> xs2
[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9]
λ>
λ> xs3 = do { i <- [1..5] ; j <- [2..4] ; return (i,j) }
λ> xs3
[(1,2),(1,3),(1,4),(2,2),(2,3),(2,4),(3,2),(3,3),(3,4),(4,2),(4,3),(4,4),(5,2),(5,3),(5,4)]
λ>
λ> length xs1
15
λ> length xs2
15
λ> length xs3
15
λ>
据我所知,这是完全标准的行为,Haskell 与 C、C++、Fortran、Python ...
共享C++ 等效示例:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> vi{1,2,3,4,5};
std::vector<int> vj{2,3,4};
for (int i: vi)
for (int j: vj)
std::cout << i << ", ";
std::cout << std::endl;
return EXIT_SUCCESS;
}
C++ 输出:
$ ./a.out
1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5,
$
I've also tried the equavalent form in List-comprehension and the result is the same
好主意。碰巧对于列表,do
表示法与列表理解完全相同。 (实际上,a syntactic extension 允许您对任何 monad 使用 list-comprehension 表示法,就像您可以对任何 monad 使用 do
表示法一样。)
所以,您问的是为什么 [a | a<-[0,1], b<-[2,3]]
给出 [0,0,1,1]
而不是 [0,1]
。这看起来令人惊讶的方式是,如果您将列表推导视为 集合推导,就像您在数学中发现的那样。但是列表不是集合,尽管 Haskellers 确实经常使用列表作为集合的临时 stand-in。如果列表推导充当集合推导,则
[x | x <- [0,1,0]]
也应该只产生 [0,1]
作为结果(或者至少,它应该产生 与 [x|x<-[0,1]]
相同的 结果)。
一般来说,这种 weeding-out-duplicates 需要进行相等性检查,如果你想让它更有效率,也可以使用排序或散列方法。列表不会做任何这样的事情,所以如果你想要 set-like 行为,你应该使用 set-implementing 数据结构。 Set
and HashSet
是最常见的。