Bagaimana fold
perbedaannya tampaknya sering menjadi sumber kebingungan, jadi inilah gambaran umum yang lebih umum:
Pertimbangkan melipat daftar nilai n [x1, x2, x3, x4 ... xn ]
dengan beberapa fungsif
dan benih z
.
foldl
adalah:
- Asosiatif kiri :
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- Ekor rekursif : Ini mengulangi daftar, menghasilkan nilai sesudahnya
- Malas : Tidak ada yang dievaluasi sampai hasilnya dibutuhkan
- Mundur :
foldl (flip (:)) []
membalikkan daftar.
foldr
adalah:
- Asosiatif yang tepat :
f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
- Rekursif menjadi argumen : Setiap iterasi diterapkan
f
ke nilai berikutnya dan hasil melipat sisa daftar.
- Malas : Tidak ada yang dievaluasi sampai hasilnya dibutuhkan
- Meneruskan :
foldr (:) []
mengembalikan daftar yang tidak berubah.
Ada titik sedikit halus di sini bahwa perjalanan orang sampai kadang-kadang: Karena foldl
adalah mundur setiap aplikasi dari f
ditambahkan ke luar dari hasil; dan karena malas , tidak ada yang dievaluasi hingga hasilnya diperlukan. Ini berarti bahwa untuk menghitung bagian mana pun dari hasil, Haskell pertama-tama melakukan iterasi melalui seluruh daftar yang membuat ekspresi aplikasi fungsi bersarang, lalu mengevaluasi fungsi terluar , mengevaluasi argumennya sesuai kebutuhan. Jika f
selalu menggunakan argumen pertamanya, ini berarti Haskell harus mengulang hingga ke istilah paling dalam, lalu bekerja menghitung mundur setiap aplikasi f
.
Ini jelas jauh dari rekursi-ekor efisien yang paling dikenal dan disukai oleh sebagian besar programmer fungsional!
Faktanya, meskipun foldl
secara teknis rekursif-ekor, karena seluruh ekspresi hasil dibuat sebelum mengevaluasi apa pun,foldl
dapat menyebabkan tumpukan melimpah!
Di sisi lain, pertimbangkan foldr
. Ini juga malas, tetapi karena berjalan ke depan , setiap aplikasi f
ditambahkan ke bagian dalam hasil. Jadi, untuk menghitung hasilnya, Haskell membuat aplikasi fungsi tunggal , yang argumen kedua adalah daftar lipat lainnya. Jika f
malas dalam argumen keduanya - konstruktor data, misalnya - hasilnya akan malas secara bertahap , dengan setiap langkah lipatan dihitung hanya ketika beberapa bagian dari hasil yang membutuhkannya dievaluasi.
Jadi kita dapat melihat mengapa foldr
kadang - kadang bekerja pada daftar tak terbatas jika foldl
tidak: Yang pertama dapat dengan malas mengonversi daftar tak hingga menjadi struktur data tak terbatas malas lainnya, sedangkan yang terakhir harus memeriksa seluruh daftar untuk menghasilkan bagian mana pun dari hasil. Di sisi lain, foldr
dengan fungsi yang membutuhkan kedua argumen dengan segera, seperti (+)
, berfungsi (atau lebih tepatnya, tidak berfungsi) seperti foldl
, membangun ekspresi yang sangat besar sebelum mengevaluasinya.
Jadi dua hal penting yang perlu diperhatikan adalah ini:
foldr
dapat mengubah satu struktur data rekursif malas menjadi yang lain.
- Jika tidak, lipatan malas akan macet dengan tumpukan melimpah pada daftar besar atau tak terbatas.
Anda mungkin telah memperhatikan bahwa sepertinya foldr
bisa melakukan apa saja foldl
, dan lebih banyak lagi. Ini benar! Faktanya, foldl hampir tidak berguna!
Tetapi bagaimana jika kita ingin menghasilkan hasil yang tidak malas dengan melipat daftar besar (tetapi tidak tak terbatas)? Untuk ini, kami menginginkan lipatan yang ketat , yang dengan cermat disediakan oleh pustaka standar :
foldl'
adalah:
- Asosiatif kiri :
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- Ekor rekursif : Ini mengulangi daftar, menghasilkan nilai sesudahnya
- Ketat : Setiap aplikasi fungsi dievaluasi sepanjang proses
- Mundur :
foldl' (flip (:)) []
membalikkan daftar.
Karena foldl'
ini ketat , untuk menghitung hasil Haskell akan mengevaluasi f
pada setiap langkah, alih-alih membiarkan argumen kiri menumpuk besar, ekspresi unevaluated. Ini memberi kita rekursi ekor biasa dan efisien yang kita inginkan! Dengan kata lain:
foldl'
dapat melipat daftar besar secara efisien.
foldl'
akan bertahan dalam putaran tak terbatas (tidak menyebabkan tumpukan melimpah) pada daftar tak terbatas.
Wiki Haskell memiliki halaman yang membahas hal ini juga.
foldr
lebih baik daripadafoldl
di Haskell , sedangkan sebaliknya di Erlang (yang saya pelajari sebelum Haskell ). Karena Erlang tidak malas dan fungsinya tidak kari , makafoldl
di Erlang berperilaku seperti difoldl'
atas. Ini jawaban yang bagus! Kerja bagus dan terima kasih!foldl
sebagai "terbelakang" danfoldr
sebagai "maju" bermasalah. Ini sebagian karenaflip
diterapkan(:)
pada ilustrasi mengapa lipatan terbalik. Reaksi alaminya adalah, "tentu saja itu mundur: Anda membuatflip
daftar!" Ini juga aneh untuk melihat yang disebut "mundur" karenafoldl
berlakuf
untuk elemen daftar pertama pertama (paling dalam) dalam evaluasi lengkap. Itu adalahfoldr
"berjalan mundur", yang diterapkanf
ke elemen terakhir terlebih dahulu.foldl
danfoldr
dan mengabaikan ketelitian dan pengoptimalan, pertama berarti "paling luar", bukan "paling dalam". Inilah sebabnya mengapafoldr
dapat memproses daftar tak terbatas danfoldl
tidak bisa - lipatan kanan pertama-tama berlakuf
untuk elemen daftar pertama dan hasil (tidak dievaluasi) melipat ekor, sedangkan lipatan kiri harus melintasi seluruh daftar untuk mengevaluasi penerapan terluarf
.last xs = foldl (\a z-> z) undefined xs
.dll.
Secara intuitif,
foldl
selalu berada di "luar" atau di "kiri" sehingga lebih dulu diperluas. Ad infinitum.sumber
Anda dapat melihat dalam dokumentasi Haskell di sini bahwa foldl adalah rekursif-ekor dan tidak akan pernah berakhir jika diteruskan daftar tak terbatas, karena ia memanggil dirinya sendiri pada parameter berikutnya sebelum mengembalikan nilai ...
sumber
Saya tidak tahu Haskell, tetapi dalam Skema,
fold-right
akan selalu 'bertindak' pada elemen terakhir dari daftar terlebih dahulu. Dengan demikian, is tidak akan berfungsi untuk daftar siklik (yang sama dengan daftar tak hingga).Saya tidak yakin apakah
fold-right
dapat ditulis rekursif-ekor, tetapi untuk daftar siklik apa pun Anda harus mendapatkan stack overflow.fold-left
OTOH biasanya diimplementasikan dengan rekursi ekor, dan hanya akan terjebak dalam loop tak terbatas, jika tidak menghentikannya lebih awal.sumber