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
fke 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 foldladalah mundur setiap aplikasi dari fditambahkan 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 fselalu 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 foldlsecara 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 fditambahkan ke bagian dalam hasil. Jadi, untuk menghitung hasilnya, Haskell membuat aplikasi fungsi tunggal , yang argumen kedua adalah daftar lipat lainnya. Jika fmalas 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 foldrkadang - kadang bekerja pada daftar tak terbatas jika foldltidak: 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, foldrdengan 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 foldrbisa 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.
foldrlebih baik daripadafoldldi Haskell , sedangkan sebaliknya di Erlang (yang saya pelajari sebelum Haskell ). Karena Erlang tidak malas dan fungsinya tidak kari , makafoldldi Erlang berperilaku seperti difoldl'atas. Ini jawaban yang bagus! Kerja bagus dan terima kasih!