基本的なdoブロック構文の説明が必要

Aug 19 2020

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]

その出力の背後にあるロジックがわかりません。その理由はモナドにあるのではないかと思いますが、関数型プログラミングは初めてですので、少し説明していただければと思います。

リスト内包表記の同等の形式も試しましたが、結果は同じです。つまり、ここで誤解した基本的なことがあります。

回答

5 leftaroundabout Aug 19 2020 at 13:08

リスト内包表記で同等の形式も試しましたが、結果は同じです。

良いアイデア。リストの場合、do表記はリスト内包表記とまったく同じになります。(実際、任意のモナドに表記法を使用できるように、任意のモナドにリスト内包表記法を使用できる構文拡張がありますdo。)

だから、あなたはなぜの代わりに[a | a<-[0,1], b<-[2,3]]与えるのかを尋ねています。これが驚くべきことに見えるのは、リスト内包を数学で見られるような集合内包として考える場合です。ただし、リストはセットではありませんが、Haskellersは、セットの一時的な代用としてリストを使用することがよくあります。リスト内包が集合の内包として機能した場合、[0,0,1,1][0,1]

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

また[0,1]、その結果としてのみ生成される必要があります(または、少なくとも、同じ結果が生成される必要[x|x<-[0,1]]があります)。

一般に、この種の除草重複は同等性チェックを必要とし、それを効率的にしたい場合は、順序付けまたはハッシュ方法のいずれかも必要です。リストはそのようなことをしません。したがって、セットのような動作が必要な場合は、セットを実装するデータ構造を使用する必要があります。SetそしてHashSet最も一般的です。

6 jpmarinier Aug 19 2020 at 12:54

これは、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, $