Perlu penjelasan untuk sintaks do block dasar
Di ghci, saya menulis:
let x = do
i <- [1..5]
j <- [2..4]
return i
Hasil yang diharapkan:
[1,2,3,4,5]
Hasil sebenarnya:
[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
Saya tidak mengerti logika di balik keluaran itu. Saya pikir alasannya mungkin sesuatu tentang monad, tetapi saya sangat baru dalam pemrograman fungsional, saya berharap seseorang dapat menjelaskannya sedikit.
Saya juga mencoba bentuk ekivalen dalam Pemahaman daftar dan hasilnya sama, yang berarti ada sesuatu yang mendasar yang saya salah paham di sini.
Jawaban
Saya juga mencoba bentuk ekivalen dalam Pemahaman daftar dan hasilnya sama
Ide bagus. Kebetulan untuk daftar, do
notasi melakukan hal yang persis sama dengan pemahaman daftar. (Faktanya, ada ekstensi sintaksis yang memungkinkan Anda menggunakan notasi pemahaman daftar untuk monad apa pun, seperti Anda dapat menggunakan do
notasi untuk monad apa pun.)
Jadi, Anda bertanya mengapa [a | a<-[0,1], b<-[2,3]]
memberi, [0,0,1,1]
bukan [0,1]
. Cara ini terlihat mengejutkan adalah jika Anda berpikir tentang pemahaman daftar sebagai pemahaman himpunan seperti yang Anda temukan dalam matematika. Tetapi daftar bukanlah kumpulan, meskipun Haskeller sering menggunakan daftar sebagai pengganti sementara untuk kumpulan. Jika pemahaman daftar bertindak sebagai pemahaman himpunan, maka
[x | x <- [0,1,0]]
juga harus menghasilkan hanya [0,1]
sebagai hasilnya (atau setidaknya, harus menghasilkan hasil yang sama seperti sebelumnya [x|x<-[0,1]]
).
Secara umum, penyiangan-keluar-duplikat semacam ini membutuhkan pemeriksaan kesetaraan, dan jika Anda ingin membuatnya efisien juga dengan metode pemesanan atau hashing. Daftar tidak melakukan hal seperti itu, jadi jika Anda menginginkan perilaku seperti kumpulan, Anda harus menggunakan struktur data yang mengimplementasikan kumpulan. Setdan HashSetyang paling umum.
Ini karena mekanisme do tidak peduli (untungnya) tentang apakah kode terdalam benar-benar merujuk ke (beberapa) variabel loop atau tidak.
Lihat Anda selalu mendapatkan nilai 3 * 5 = 15 terlepas dari kode terdalam:
λ>
λ> 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
λ>
Sejauh yang saya tahu, ini adalah perilaku standar sempurna, yang Haskell bagikan dengan C, C ++, Fortran, Python ...
Contoh setara 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;
}
Keluaran C ++:
$ ./a.out 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, $