Mengetahui jawaban jbapple yang sangat baik tentang replicate
, tetapi menggunakan replicateA
(yang replicate
dibangun di atas), saya datang dengan yang berikut:
--Unlike fromList, one needs the length explicitly.
myFromList :: Int -> [b] -> Seq b
myFromList l xs = flip evalState xs $ Seq.replicateA l go
where go = do
(y:ys) <- get
put ys
return y
myFromList
(dalam versi yang sedikit lebih efisien) sudah didefinisikan dan digunakan secara internal dalam Data.Sequence
untuk membangun pohon jari yang hasil macam.
Secara umum, intuisi untuk replicateA
sederhana. replicateA
dibangun di atas fungsi applicativeTree . applicativeTree
mengambil sepotong pohon ukuran m
, dan menghasilkan pohon seimbang yang mengandung n
salinan ini. Kasing n
hingga 8 (satu Deep
jari) dikodekan dengan keras. Apa pun di atas ini, dan itu memanggil dirinya sendiri secara rekursif. Unsur "aplikatif" hanyalah bahwa ia menyisipkan konstruksi pohon dengan efek threading melalui, seperti, dalam kasus kode di atas, menyatakan.
The go
fungsi, yang direplikasi, hanyalah sebuah tindakan yang mendapat kondisi saat ini, muncul sebuah elemen dari atas, dan menggantikan sisanya. Pada setiap doa, itu dengan demikian melangkah lebih jauh ke bawah daftar yang disediakan sebagai input.
Beberapa catatan lebih konkret
main = print (length (show (Seq.fromList [1..10000000::Int])))
Pada beberapa tes sederhana, ini menghasilkan tradeoff kinerja yang menarik. Fungsi utama di atas berjalan hampir 1/3 lebih rendah dengan myFromList daripada dengan fromList
. Di sisi lain, myFromList
digunakan tumpukan konstan 2MB, sedangkan standar fromList
digunakan hingga 926MB. 926MB itu muncul dari keharusan untuk menahan seluruh daftar di memori sekaligus. Sementara itu, solusi dengan myFromList
mampu mengkonsumsi struktur dengan cara streaming malas. Masalah dengan kecepatan hasil dari fakta yang myFromList
harus melakukan alokasi kira-kira dua kali lebih banyak (sebagai akibat dari pembangunan pasangan / penghancuran monad negara) sebagaifromList
. Kita dapat menghilangkan alokasi tersebut dengan pindah ke state monad yang diubah CPS, tetapi hal itu menghasilkan memori yang jauh lebih banyak pada waktu tertentu, karena kehilangan kemalasan mengharuskan untuk melintasi daftar secara non-streaming.
Di sisi lain, jika alih-alih memaksa seluruh urutan dengan sebuah pertunjukan, saya pindah ke hanya mengekstraksi kepala atau elemen terakhir, myFromList
segera menyajikan kemenangan yang lebih besar - mengekstraksi elemen kepala hampir instan, dan mengekstraksi elemen terakhir adalah 0,8 detik. . Sementara itu, dengan standar fromList
, mengekstraksi kepala atau elemen terakhir membutuhkan biaya ~ 2,3 detik.
Ini semua detail, dan merupakan konsekuensi dari kemurnian dan kemalasan. Dalam situasi dengan mutasi dan akses acak, saya akan membayangkan replicate
solusinya benar-benar lebih baik.
Namun, hal itu menimbulkan pertanyaan apakah ada cara untuk menulis ulang applicativeTree
sedemikian rupa sehingga myFromList
lebih efisien. Masalahnya adalah, saya pikir, bahwa tindakan aplikatif dijalankan dalam urutan yang berbeda dari pohon secara alami dilalui, tetapi saya belum sepenuhnya bekerja melalui bagaimana ini bekerja, atau jika ada cara untuk menyelesaikan ini.