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!