15

Gentle Introductionを読んでいて、2つのジェネレーターを含むリスト内包表記で、右端のジェネレーターが「最速」で繰り返されるのはなぜか疑問に思っています(つまり、最も内側のループにコンパイルされると思います)。次のGHCi出力を確認します。

*Main> concat [[(x,y) | x <- [0..2]] | y <- [0..2]]
[(0,0),(1,0),(2,0),(0,1),(1,1),(2,1),(0,2),(1,2),(2,2)]
*Main> [(x,y) | x <- [0..2], y <- [0..2]]
[(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)]

左端のジェネレーターが最も速く繰り返された場合、上記の2つの式は同じ値になります。これにより、この規則を選択するのが自然になります。

では、なぜ反対の規則が選ばれたのか誰かが知っていますか?PythonにはHaskellと同じ規則があることに気付きました(おそらくHaskellから借りたのでしょうか?)。Pythonの世界では、「forループを作成する順序であるため」という順序が選択されたようですが、私は、forループの観点から考えることは、ほとんどのHaskellプログラマーが行うこととまったく同じではないことを収集します...

考え?


以下のルイ・ワッサーマンの答えに対する私のコメントから:

ここでは、命令型の理解の説明に対応する順序は、リストのネストに対応するよりも自然であると考えられていたと思います。つまり、本質的に、これに関するHaskellの説明は、質問でリンクしたPythonの説明と同じです。結局のところ、そう思われます。

4

3 に答える 3

23

そのため、物事はまともな方法で範囲を定めます。

[(x, y) | x <- [1..10], y <- [1..x]]

理にかなっています-x理解の範囲内ですy-しかし

[(x, y) | y <- [1..x], x <- [1..10]]

やや意味がありません。

doさらに、このように、モナド構文と一致しています。

do x <- [1..10]
   y <- [1..x]
   return (x, y)
于 2012-05-01T15:03:44.433 に答える
6

doリスト内包表記を最初に表記法に拡張し、次にモナディックバインドに拡張すると、より理にかなっている場合があります。すでにバインドされている名前を参照する理解を書きたいとしましょう。

[ (x,y) | x <- [1,2,3], y <- [x+1,x+2] ]

これはに拡張されます

do x <- [1,2,3]
   y <- [x+1,x+2]
   return (x,y)

これはに拡張されます

[1,2,3] >>= \x ->
[x+1,x+2] >>= \y -> 
return (x,y)

xこれにより、必要なときに正確に範囲内にあることが明確になります。

表記への拡張doが左から右ではなく右から左に行われた場合、元の式は次のように拡張されます。

[x+1,x+2] >>= \y ->
[1,2,3] >>= \x ->
return (x,y)

which is clearly nonsensical - it refers to the value of x in a scope where x is not yet bound. So we'd have to write our original comprehension as

[ (x,y) | y <- [x+1,x+2], x <- [1,2,3] ]

to get the result we wanted, which seems unnatural - at the time your eye scans over the phrase y <- [x+1,x+2] you don't actually know what x is. You'd have to read the comprehension backwards to find out.

So it'd didn't need to be the case that the right-most binding is unrolled into the "inner loop" but it makes sense when you consider that humans are going to have to read the resulting code.

于 2012-05-01T16:00:08.130 に答える
0

実際、Pythonはリスト内包表記にHaskellと同じスコープ構造を使用しています。

Haskellを比較してください:

*Main> concat [[(x,y) | x <- [0..2]] | y <- [0..2]]
[(0,0),(1,0),(2,0),(0,1),(1,1),(2,1),(0,2),(1,2),(2,2)]
*Main> [(x,y) | x <- [0..2], y <- [0..2]]
[(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)]

このPythonで:

>>> from itertools import chain
>>> list(chain(*[[(x,y) for x in range(3)] for y in range(3)]))
[(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)]
>>> [(x,y) for x in range(3) for y in range(3)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
>>> 
于 2012-05-01T15:09:43.340 に答える