简体   繁体   中英

Need explanation for basic do block syntax

In ghci, I wrote:

 let x = do
    i <- [1..5]
    j <- [2..4]
    return i 

Expected result:

[1,2,3,4,5]

Actual result:

[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]

I don't understand the logic behind that output. I think the reason might be something about monad, but I am very new to functional programming, I wish someone could explain it a little bit.

I've also tried the equavalent form in List-comprehension and the result is the same, which means there is something basic I misunderstood here.

This is because the do mechanism does not care (fortunately) about whether or not the innermost code actually refers to (some of) the loop variables.

See you always get 3*5=15 values regardless of innermost code:

 λ> 
 λ> 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
 λ> 

As far as I can tell, this is perfectly standard behavior, that Haskell shares with C, C++, Fortran, Python...

A C++ equivalent example:

#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++ output:

$ ./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

Good idea. It so happens that for lists, do notation does exactly the same as list comprehensions. (In fact, there'sa syntactic extension that allows you to use list-comprehension notation for any monad, like you can use do notation for any monad.)

So, you're asking why [a | a<-[0,1], b<-[2,3]] [a | a<-[0,1], b<-[2,3]] gives [0,0,1,1] instead of [0,1] . The way this looks surprising is if you think about list comprehensions as set comprehensions like you'd find in maths. But lists aren't sets, though Haskellers do often use lists as a makeshift stand-in for sets. If lists comprehensions acted as set comprehension, then

  [x | x <- [0,1,0]]

should also yield only [0,1] as its result (or at least, it should yield the same result as [x|x<-[0,1]] does).

In general this sort of weeding-out-duplicates requires equality checks, and if you want to make it efficient also either an ordering or a hashing method. Lists don't do any such thing, so if you want set-like behaviour you should use a set-implementing data structure. Set and HashSet are the most common.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM