Наскільки foldрізниця здається частим джерелом плутанини, ось ось більш загальний огляд:
Розглянемо складання списку з n значень [x1, x2, x3, x4 ... xn ]з деякою функцією fта насінням z.
foldl є:
- Лівий асоціативний :
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- Хвост рекурсивний : він повторюється через список, створюючи значення згодом
- Ледачий : Нічого не оцінюється, поки результат не потрібен
- Назад :
foldl (flip (:)) []повертає список.
foldr є:
- Правий асоціативний :
f x1 (f x2 (f x3 (f x4 ... (f xn z) ... )))
- Рекурсивний аргумент : Кожна ітерація стосується
fнаступного значення та результату складання решти списку.
- Ледачий : Нічого не оцінюється, поки результат не потрібен
- Вперед :
foldr (:) []повертає список незмінним.
Там є трохи тонкий момент тут , що поїздки люди до іноді: Тому що foldlце в зворотному напрямку кожного застосування fдодаються до зовнішній стороні результату; і тому, що він лінивий , нічого не оцінюють, поки не буде потрібен результат. Це означає, що для обчислення будь-якої частини результату Haskell спочатку повторює весь список, будуючи вираз вкладених додатків функції, потім оцінює найбільш віддалену функцію, оцінюючи її аргументи за потребою. Якщо fзавжди використовується перший аргумент, це означає, що Haskell повинен повторюватись до самого внутрішнього терміна, а потім працювати назад, обчислюючи кожну програму f.
Це, очевидно, далеко від ефективної хвостової рекурсії, яку знають і люблять більшість функціональних програмістів!
Насправді, навіть якщо foldlтехнічно є рекурсивним, оскільки весь вираз результату будується перед тим, як щось оцінити, це foldlможе спричинити переповнення стека!
З іншого боку, врахуйте foldr. Це також ліниво, але оскільки він працює вперед , кожне застосування fдодається до внутрішньої частини результату. Отже, для обчислення результату Haskell будує єдину функціональну програму, другий аргумент якої - решта складеного списку. Якщо, наприклад f, лінь у другому аргументі - конструкторі даних - результат буде поступово ледачим , при цьому кожен крок складки обчислюється лише тоді, коли буде оцінена деяка частина результату, яка потребує цього.
Тож ми можемо зрозуміти, чому foldrіноді працює над нескінченними списками, коли foldlцього немає: Перший може ліниво перетворити нескінченний список в іншу ліниву нескінченну структуру даних, тоді як останній повинен перевірити весь список, щоб створити будь-яку частину результату. З іншого боку, foldrфункція, яка потребує обох аргументів негайно, наприклад (+), працює (а точніше, не працює) дуже схоже foldl, будуючи величезний вираз перед оцінкою.
Отже, два важливі моменти, які слід зазначити, це:
foldr може перетворити одну ледачу рекурсивну структуру даних в іншу.
- В іншому випадку ліниві складки зіткнуться з переповненням стека у великих чи нескінченних списках.
Можливо, ви помітили, що це здається, що foldrможна робити все foldl, плюс більше. Це правда! Насправді, foldl майже марний!
Але що робити, якщо ми хочемо отримати не лінивий результат, склавши над великим (але не нескінченним) списком? Для цього ми хочемо сувору складку , яку стандартні бібліотеки хоч і надають :
foldl' є:
- Лівий асоціативний :
f ( ... (f (f (f (f z x1) x2) x3) x4) ...) xn
- Хвост рекурсивний : він повторюється через список, створюючи значення згодом
- Суворо : кожне застосування функції оцінюється по ходу
- Назад :
foldl' (flip (:)) []повертає список.
Тому що foldl'це суворе , щоб обчислити результат Haskell буде оцінювати f на кожному кроці, замість того , щоб дозволити лівий аргумент накопичити величезний, невичісленного вираз. Це дає нам звичайну та ефективну хвостову рекурсію, яку ми хочемо! Іншими словами:
foldl' може складати великі списки ефективно.
foldl' буде висіти в нескінченному циклі (не викликати переповнення стека) у нескінченному списку.
У вікі Haskell є також сторінка, де це обговорюється .
foldrце краще , ніжfoldlв Haskell , а навпаки , в Erlang (який я дізнався , перш ніж Haskell ). Оскільки Ерланг не лінивий і функції не криються , тоfoldlв Ерланге поводиться так, якfoldl'вище. Це чудова відповідь! Гарна робота і спасибі!