Наскільки 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'
вище. Це чудова відповідь! Гарна робота і спасибі!