2 つの単純なリスト内包表記を考えてみましょう。
ex1 = [(a,b) | a <- [1..3], b <- [1..3]]
ex2 = [(a,b) | a <- [1..3], b <- [1..a]]
それらはほとんど同じですが、2 番目のケースでは、b
範囲は から1
までであり、からまでa
ではありません。それらが何に等しいかを考えてみましょう。要点を明確にするために、値をフォーマットしました。1
3
ex1 = [ (1,1), (1,2), (1,3)
, (2,1), (2,2), (2,3)
, (3,1), (3,2), (3,3) ]
ex2 = [ (1,1),
, (2,1), (2,2),
, (3,1), (3,2), (3,3) ]
最初の例では、リスト内包表記は[1..3]
とから可能な要素のすべての組み合わせを引き出し[1..3]
ます。しかし、セットではなくリストについて話しているので、その順序が重要です。したがって、より詳細には、ex1
実際に意味することは次のとおりです。
a
そのリストから可能なすべての値と等しくし
ましょう。
- の各値について
a
、b
そのリストから考えられるすべての値を とします。
または、言い換えると、「 のすべての可能な値について、 のすべての可能な値をa
計算(a,b)
しますb
。」結果の順序を見ると、次のようになります。
- 最初の 3 つの要素で
a
は、 は に等しく1
、 のすべての値と対になっていることがわかりますb
。
- 次の 3 つの要素で
a
は、 は に等しく2
、 のすべての値が表示されますb
。
- 最後に、最後の 3 つの要素について、
a
は に等しく、3
のすべての値が表示されますb
。
2 番目のケースでは、ほとんど同じことが起こります。しかし、最初にa
選択されるため、それに依存できます。したがって:b
- まず、
a
は に等しく1
、 のすべての可能な値と対になっていることがわかりますb
。b <- [1..a]
ですから、それは を意味するので、b <- [1..1]
選択肢は 1 つしかありません。
- 1 つの要素の後、
a
は に等しくなり2
、 のすべての可能な値と対になっていることがわかりますb
。これは を意味するb <- [1..2]
ので、2 つの結果が得られます。
- 最後に、
a
に等しい3
ので、b <- [1..3]
;を選択します。これにより、3 つの結果の完全なセットが得られます。
つまり、リスト内包表記は順序付けに依存しているため、それを利用できます。それを確認する 1 つの方法は、これらのリスト内包表記をネストされたリスト内包表記に変換することを想像することです。
ex1 = concat [ [(a,b) | b <- [1..3]] | a <- [1..3] ]
ex2 = concat [ [(a,b) | b <- [1..a]] | a <- [1..3] ]
正しい振る舞いをするためにはa <- [1..3]
、外に出なければなりません。これにより、b
s が s よりも速く変化することが保証されa
ます。そして、うまくいけば、どのようb
に に依存できるかが明確になりa
ます。別の翻訳 (基本的にHaskell 2010 Report で使用されているもの) は次のようになります。
ex1 = concatMap (\a -> [(a,b) | b <- [1..3]]) [1..3]
= concatMap (\a -> concatMap (\b -> [(a,b)]) [1..3]) [1..3]
ex2 = concatMap (\a -> [(a,b) | b <- [1..a]]) [1..3]
= concatMap (\a -> concatMap (\b -> [(a,b)]) [1..a]) [1..3]
繰り返しますが、これにより、従うのが難しい場合でも、ネストが非常に明示的になります。心に留めておくべきことは、 の選択が最初に行われる場合、それはリスト内包表記の内側にあっても、翻訳された式の外側a
になければならないということです。の完全な正式な翻訳は、次のようになります。rightTriangles
rightTriangles =
concatMap (\c ->
concatMap (\b ->
concatMap (\a ->
if a^2 + b^2 == c^2
then [(a,b,c)]
else []
) [1..b]
) [1..c]
) [1..10]
補足として、別の書き方rightTriangles
は次のとおりです。
import Control.Monad (guard)
rightTriangles = do c <- [1..10]
b <- [1..c]
a <- [1..b]
guard $ a^2 + b^2 == c^2
return (a,b,c)
あなたはおそらくdo
まだ記法を使ったことがなく、確かに 以外には使っていないIO
ので、必ずしもこれを理解する必要があると言っているわけではありません。x <- list
しかし、 「 for each x
in 」と言っている行を読むことができるlist
ので、これをネストされたループとして読んでください。
rightTriangles = do
c <- [1..10] -- For each `c` from `1` to `10`, ...
b <- [1..c] -- For each `b` from `1` to `c`, ...
a <- [1..b] -- For each `a` from `1` to `b`, ...
guard $ a^2 + b^2 == c^2 -- If `a^2 + b^2 /= c^2`, then `continue` (as in C);
return (a,b,c) -- `(a,b,c)` is the next element of the output list.
この解釈では、 のみが最も内側continue
のループの次の繰り返しにスキップすることに注意してください。次のように書くこともできます
rightTriangles = do c <- [1..10]
b <- [1..c]
a <- [1..b]
if a^2 + b^2 == c^2
then return (a,b,c)
else [] -- or `mzero`
最後の行には、「の場合は出力リストa^2 + b^2 == c^2
に追加し、そうでない場合は何も追加しない」と書かれています。(a,b,c)
このように書かれているのを見ると、進行中の「ネストされたループ」タイプの構造を明確にするのに役立つかもしれないと思ったので、これについて言及するだけであり、 Learn You A Haskelldo
の第2章を読んで -notation を完全に理解する必要があるからではありません:-)