2012-05-01 7 views
14

Sto leggendo il Gentle Introduction e mi sto chiedendo perché in una lista di comprensione con due generatori, il generatore più a destra è iterato "il più veloce" (cioè compila il ciclo più interno, immagino). Osservare il seguente output GHCi:Perché la comprensione delle liste Haskell con più generatori considera il generatore più a destra come il ciclo più stretto?

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

Se il generatore più a sinistra sono stati iterata più veloce, queste due espressioni avrebbero lo stesso valore, che a mio avviso rende la scelta di questa convenzione più naturale in qualche modo.

Così qualcuno sa perché è stata scelta la convenzione opposta? Ho notato che Python ha la stessa convenzione di Haskell (forse l'ha anche preso in prestito da Haskell?), E nel mondo Python the word sembra che l'ordine sia stato scelto "perché è l'ordine in cui si scrive un ciclo for", ma io Raccogliere che pensare in termini di cicli for non è esattamente ciò che la maggior parte dei programmatori Haskell fa ...

Pensieri?


Dal mio commento sulla risposta di Louis Wasserman di seguito:

immagino qui l'ordine corrispondente ad una spiegazione imperativo stile della comprensione è stato considerato più naturale di averlo corrisponde nidificazione lista. Quindi in sostanza la spiegazione di Haskell per questo è la stessa della spiegazione Python che ho inserito nella domanda, dopotutto, sembra.

+0

Come sarebbe * più naturale *? Vuoi anche 11, 21, 31, 41 invece di 11, 12, 13, 14? – Ingo

+0

Immagino che usare '(y, x)' come espressione prototipica - o mettere il generatore y alla sinistra del generatore x - avrebbe più senso se il generatore più a sinistra fosse il loop più stretto. Quindi sarebbe la seconda riga della mia uscita GHCi che ti sembrò strana (11, 21, 31, 41), piuttosto che la prima, ma sarebbero comunque diversi l'una dall'altra, il che è il mio punto. – kini

+0

Scusa, suppongo che dovrei rivolgermi a te quando rispondo a te, @Ingo. (Tipo di nuovo per l'overflow dello stack.) – kini

risposta

21

Così che le cose vanno bene.

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

ha un senso - x è portata per la comprensione su y - ma

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

rende un po 'meno senso.

Inoltre, in questo modo è coerente con la sintassi do monade:

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

Potrebbe essere una buona idea mostrare come sarebbe lo stesso codice usando la sintassi monade 'do', per chi non lo sapesse. – dflemstr

+0

Hmm. Come principiante, non vedo davvero come la definizione dell'ambito sia meno arbitraria dell'ordinanza dei generatori. Ancora usando il mio esempio di nidificazione, '[[(x, y) | x <- [1..10]] | y <- [1..x]] 'non ha senso, mentre' [[(x, y) | y <- [1..x]] | x <- [1..10]] 'ha senso. – kini

+2

L'annidamento è (e deve essere) completamente diverso dalla sintassi di comprensione concatenata. In ogni caso, la notazione 'do' ha un ordine univoco che concorda con la comprensione delle liste. –

0

realtà Python utilizza la stessa struttura portata di Haskell per list comprehension.

Confronta il tuo 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 questo 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

Oops, lo sapevi già. In ogni modo, lascerò questo qui per il confronto. –

+0

Ehm, sì, questo è quello che intendevo :) Modificherò la mia domanda per chiarezza. – kini

6

può rendere più senso se si espande al di lista prima in do notazione e poi in lega monadici.Diciamo che vogliamo scrivere la comprensione in cui ci si riferisce di nuovo a nomi che sono già vincolati:

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

Questo si espande per

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

che si espande a

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

che mette in chiaro che x è nello scope esattamente quando deve essere.

Se l'espansione in do notazione è accaduto da destra a sinistra invece che da sinistra a destra, allora la nostra espressione originale si espanderebbe in

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

che è chiaramente priva di senso - si riferisce al valore della x in un ambito in cui x non è ancora associato. Così avremmo dovuto scrivere la nostra comprensione originale come

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

per ottenere il risultato che volevamo, che sembra innaturale - al momento le scansioni degli occhi oltre la frase y <- [x+1,x+2] in realtà non sanno che cosa è x. Dovresti leggere la comprensione all'indietro per scoprirlo.

Quindi che sarebbe non aveva bisogno essere il caso che il più a destra legame è srotolato nel "ciclo interno", ma ha senso se si considera che gli umani stanno per avere a leggere il codice risultante .

+0

Sì, tu fai punti simili alla risposta di Louis Wasserman, penso.Ma se il ragionamento è veramente allineato con l'ordinamento degli obiettivi da sinistra a destra per adattarsi agli umani (di lingua inglese) che leggono da sinistra a destra, allora perché l'espressione ('(x, y)') sulla sinistra del quantificazioni delle sue variabili? In realtà, perché non seguire l'ordine monodico delle istruzioni fino in fondo, in modo che '[1,2,3] >> = \ x -> [x + 1, x + 2] >> = \ y -> return (x, y) 'è stato zuccherato come, ad esempio,' [x <- [1,2,3], y <- [x + 1, x + 2] | (X, y)] '? – kini

+0

Per dirla in un altro modo, vero, quando esegui la scansione di 'y <- [x + 1, x + 2]', in realtà non sai cosa sia 'x', ma quando esegui la scansione di' (x, y) ' , non sai neanche quale sia 'x' o' y'. – kini

+2

Questo è un punto valido. Sospetto che il motivo per cui lo scrivo in questo modo sia parallelo alla notazione matematica [set-builder] (http://en.wikipedia.org/wiki/Set-builder_notation#Parallels_in_programming_languages) (ricorda che Haskell ha molte influenze dalla matematica). Nel caso ti aiuti, leggo sempre la barra verticale '|" come "dove" (l'ho letto come "tale che" nei giorni in cui mi consideravo più un matematico che un programmatore ...) –