È necessaria una spiegazione per la sintassi di base del blocco do
In ghci ho scritto:
let x = do
i <- [1..5]
j <- [2..4]
return i
Risultato atteso:
[1,2,3,4,5]
Risultato attuale:
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
Non capisco la logica dietro quell'output. Penso che il motivo potrebbe essere qualcosa su monade, ma sono molto nuovo nella programmazione funzionale, vorrei che qualcuno potesse spiegarlo un po '.
Ho anche provato la forma equivalente in Comprensione delle liste e il risultato è lo stesso, il che significa che c'è qualcosa di fondamentale che ho frainteso qui.
Risposte
Ho anche provato la forma uguale in List-Understension e il risultato è lo stesso
Buona idea. Accade così che per le liste, la do
notazione faccia esattamente la stessa cosa delle comprensioni di lista. (In effetti, esiste un'estensione sintattica che ti consente di utilizzare la notazione di comprensione della lista per qualsiasi monade, come puoi usare la do
notazione per qualsiasi monade.)
Quindi, stai chiedendo perché [a | a<-[0,1], b<-[2,3]]
dà [0,0,1,1]
invece di [0,1]
. Il modo in cui questo sembra sorprendente è se pensi alle comprensioni di elenchi come a comprensioni di set come quelle che potresti trovare in matematica. Ma gli elenchi non sono set, anche se gli Haskeller usano spesso elenchi come sostituti improvvisati per i set. Se le liste di comprensione agivano come insiemi di comprensione, allora
[x | x <- [0,1,0]]
dovrebbe anche dare solo [0,1]
come risultato (o almeno, dovrebbe produrre lo stesso risultato [x|x<-[0,1]]
).
In generale questo tipo di eliminazione dei duplicati richiede controlli di uguaglianza e, se si desidera renderlo efficiente, anche un metodo di ordinamento o hashing. Le liste non fanno nulla del genere, quindi se vuoi un comportamento simile a un set dovresti usare una struttura dati che implementa set. Sete HashSetsono i più comuni.
Questo perché al meccanismo do non interessa (fortunatamente) se il codice più interno si riferisce effettivamente ad (alcune delle) variabili del ciclo.
Vedi che ottieni sempre 3 * 5 = 15 valori indipendentemente dal codice più interno:
λ>
λ> 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
λ>
Per quanto ne so, questo è un comportamento perfettamente standard, che Haskell condivide con C, C ++, Fortran, Python ...
Un esempio equivalente in 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;
}
Uscita C ++:
$ ./a.out 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, $