2012-05-01 13 views
14

Estoy leyendo a través del Gentle Introduction y me pregunto por qué en una lista de comprensión con dos generadores, el generador de la derecha se repite "el más rápido" (es decir, se compila en el ciclo más interno, supongo). Observe la siguiente salida GHCi:¿Por qué Haskell enumera las comprensiones con múltiples generadores que tratan el generador más a la derecha como el lazo más cerrado?

*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)] 

Si el generador de más a la izquierda se itera más rápido, las dos expresiones anteriores tendrían el mismo valor, que creo que hace que la elección de esta convención más natural de alguna manera.

¿Alguien sabe por qué se eligió la convención opuesta? Me doy cuenta de que Python tiene la misma convención que Haskell (¿tal vez incluso la tomó prestada de Haskell?), Y en el mundo de Python the word parece ser que el orden fue elegido "porque ese es el orden en el que escribirías un bucle for", pero reunir que pensar en términos de bucles for no es exactamente lo que hacen la mayoría de los programadores Haskell ...

¿Pensamientos?


Desde mi comentario sobre la respuesta de Louis Wasserman a continuación:

supongo que aquí el orden que corresponde a una explicación de estilo imperativo de la comprensión se consideró más natural de tener que corresponde con la anidación de la lista. Entonces, en esencia, la explicación de Haskell para esto es la misma que la explicación de Python que relacioné en la pregunta, después de todo, parece.

+0

¿Cómo sería esto * más natural *? ¿También quiere 11, 21, 31, 41 en lugar de 11, 12, 13, 14? – Ingo

+0

Supongo que usar '(y, x)' como expresión prototípica - o poner el generador y a la izquierda del generador x - tendría más sentido si el generador de la izquierda fuera el más ajustado. Entonces sería la segunda línea en mi salida de GHCi lo que te pareció extraño (11, 21, 31, 41), en lugar del primero, pero igual serían diferentes el uno del otro, que es mi punto. – kini

+0

Lo siento, creo que debería dirigirme a usted cuando le contesto, @Ingo. (Algo nuevo en Stack Overflow.) – kini

Respuesta

21

Para que las cosas se extiendan de una manera sensata.

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

tiene sentido - x está en el ámbito de la comprensión de y - pero

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

hace algo menos sentido.

Además, de esta manera es consistente con la sintaxis do mónada:

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

Podría ser una buena idea mostrar cómo se vería el mismo código usando la sintaxis 'do' monad, para los no iniciados. – dflemstr

+0

Hmm. Como principiante, realmente no veo cómo el alcance es menos arbitrario que el orden de los generadores. Otra vez usando mi ejemplo de anidamiento, '[[(x, y) | x <- [1..10]] | y <- [1..x]] 'no tiene sentido, mientras que' [[(x, y) | y <- [1..x]] | x <- [1..10]] 'tiene sentido. – kini

+2

El anidamiento es (y debe ser) totalmente diferente de la sintaxis de comprensión encadenada. En cualquier caso, la notación 'do' tiene un orden no ambiguo con el que las listas de comprensión están de acuerdo. –

0

En realidad Python usa la misma estructura como Haskell alcance de las listas por comprensión.

comparar sus 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)] 

con este 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)] 
>>> 
+0

Vaya, usted ya lo sabía. De cualquier forma, lo dejaré aquí para comparar. –

+0

Er, sí, eso es lo que quise decir :) Editaré mi pregunta para mayor claridad. – kini

6

Se puede tener más sentido si expande la lista por primera vez en la comprensión do notación y luego en une monádicos.Digamos que queremos escribir una comprensión cuando nos referimos de nuevo a los nombres que ya están atados:

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

Esto expande a

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

que se expande a

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

que hace que sea clara que x está en el alcance exactamente cuando debe ser.

Si la expansión en do notación pasó de derecha a izquierda en lugar de izquierda a derecha, a continuación, nuestra expresión original se expandiría en

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

que es claramente sin sentido - se refiere al valor de x en un ámbito donde x aún no está vinculado. Por lo que tendríamos que escribir nuestra comprensión original como

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

para obtener el resultado que queríamos, lo que parece poco natural - en el momento de sus exploraciones oculares más de la frase y <- [x+1,x+2] no saben realmente lo que es x. Tendría que leer la comprensión al revés para averiguarlo.

Así que no fue necesario ser el caso de que el enlace de la derecha se desenrolla en el "bucle interno", pero tiene sentido si se tiene en cuenta que los humanos van a tener que leer el código resultante .

+0

Sí, usted hace puntos similares a la respuesta de Louis Wasserman, creo.Pero si el razonamiento está realmente alineado con el orden del alcance que es de izquierda a derecha para que coincida con los humanos (de habla inglesa) que leen de izquierda a derecha, entonces ¿por qué la expresión ('(x, y)') está a la izquierda del cuantificaciones de sus variables? En realidad, ¿por qué no seguir el orden monádico de instrucciones todo el camino, de modo que '[1,2,3] >> = \ x -> [x + 1, x + 2] >> = \ y -> regresar (x, y) 'fue azucarado como, digamos,' [x <- [1,2,3], y <- [x + 1, x + 2] | (x, y)] '? – kini

+0

Para decirlo de otra manera, es cierto, cuando escanea 'y <- [x + 1, x + 2]', en realidad no sabe qué es 'x', pero cuando escanea' (x, y) ' , tampoco sabes lo que son 'x' o' y'. – kini

+2

Ese es un punto válido. Sospecho que la razón para escribirlo de esta manera es que es paralela a la notación matemática [set-builder] (http://en.wikipedia.org/wiki/Set-builder_notation#Parallels_in_programming_languages) (recuerde que Haskell tiene muchas influencias de las matemáticas). En caso de que lo ayude, siempre leí la barra vertical '|' como "dónde" (solía leerlo como "tal que" en los días cuando me consideraba más un matemático que un programador ...) –

Cuestiones relacionadas