Перш ніж проголосувати проти, будь ласка, прочитайте наступний параграф
Я публікую відповідь для тих людей, яким такий підхід може бути більше підходить для їхнього мислення. Відповідь, можливо, містить зайву інформацію та думки, але саме вона мені потрібна для вирішення проблеми. Крім того, оскільки це ще одна відповідь на те саме питання, очевидно, що вона суттєво перекривається з іншими відповідями, однак вона розповідає історію того, як я міг зрозуміти цю концепцію.
Справді, я почав записувати ці замітки як особистий запис своїх думок, намагаючись зрозуміти цю тему. Мені знадобився цілий день, щоб я зачепив його суть, якщо я це справді зрозумів.
Мій довгий шлях до розуміння цієї простої вправи
Легка частина: що нам потрібно визначити?
Що відбувається з наступним прикладом виклику
foldl f z [1,2,3,4]
можна візуалізувати за допомогою такої схеми (яка є у Вікіпедії , але я вперше побачив це за іншою відповіддю ):
_____results in a number
/
f f (f (f (f z 1) 2) 3) 4
/ \
f 4 f (f (f z 1) 2) 3
/ \
f 3 f (f z 1) 2
/ \
f 2 f z 1
/ \
z 1
(Як допоміжна примітка, при використанні foldl
кожної програми f
не виконується, і вирази обманюються так, як я писав їх вище; в принципі, їх можна обчислити, коли ви рухаєтесь знизу вгору, і саме це і foldl'
робить.)
Вправа по суті кидає нам виклик використовувати foldr
замість того foldl
, щоб відповідним чином змінити функцію кроку (тому ми використовуємо s
замість f
) та початковий накопичувач (тому ми використовуємо ?
замість z
); список залишається незмінним, інакше про що ми говоримо?
Заклик до foldr
має виглядати так:
foldr s ? [1,2,3,4]
і відповідна схема така:
_____what does the last call return?
/
s
/ \
1 s
/ \
2 s
/ \
3 s
/ \
4 ? <-
Результат дзвінка -
s 1 (s 2 (s 3 (s 4 ?)))
Що таке s
і ?
? А які їх типи? Схоже, s
це функція з двома аргументами, дуже схожа f
, але не будемо поспішати робити висновки. Крім того, ?
залишмо на мить осторонь і давайте зауважимо, що це z
має вступити в дію, як тільки 1
вступить у гру; однак, як це може z
зіграти у виклику функції може бути два аргументи s
, а саме у виклику s 1 (…)
? Ми можемо розв’язати цю частину загадки, вибравши один, s
який приймає 3 аргументи, а не 2, про які ми згадали раніше, так що найвіддаленіший виклик s 1 (…)
призведе до функції, яка приймає один аргумент, якому ми можемо передати z
!
Це означає, що нам потрібен вихідний дзвінок, який розширюється до
f (f (f (f z 1) 2) 3) 4
бути рівнозначним
s 1 (s 2 (s 3 (s 4 ?))) z
або, іншими словами, ми хочемо частково застосовану функцію
s 1 (s 2 (s 3 (s 4 ?)))
бути еквівалентною наступній лямбда-функції
(\z -> f (f (f (f z 1) 2) 3) 4)
Знову ж таки, "єдиними" частинами, які нам потрібні, є s
і ?
.
Поворотний пункт: розпізнавання складу функції
Давайте перемалюємо попередню діаграму і напишемо праворуч, що ми хочемо, щоб кожен виклик s
був еквівалентним:
s s 1 (…) == (\z -> f (f (f (f z 1) 2) 3) 4)
/ \
1 s s 2 (…) == (\z -> f (f (f z 2) 3) 4)
/ \
2 s s 3 (…) == (\z -> f (f z 3) 4)
/ \
3 s s 4 ? == (\z -> f z 4)
/ \
4 ? <-
Я сподіваюся, що з структури діаграми зрозуміло, що (…)
на кожному рядку знаходиться правий бік рядка під ним; краще, це функція, повернута з попереднього (нижче) викликуs
.
Також повинно бути ясно, що виклик s
аргументами x
і y
є (повним) застосуванням y
часткового застосування f
єдиного аргументу x
. Оскільки часткове застосування f
to x
можна записати як лямбда (\z -> f z x)
, то повне застосування y
до нього призводить до лямбди (\z -> y (f z x))
, яку в цьому випадку я б переписав як y . (\z -> f z x)
; перекладаючи слова у вираз, s
ми отримуємо
s x y = y . (\z -> f z x)
(Це те саме, що s x y z = y (f z x)
, що і те саме, що і книга, якщо ви перейменуєте змінні.)
Останній біт: яке початкове "значення" ?
акумулятора? Наведену вище діаграму можна переписати, розширивши вкладені виклики, щоб зробити їх ланцюжками композиції:
s s 1 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
/ \
1 s s 2 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
/ \
2 s s 3 (…) == (\z -> f z 4) . (\z -> f z 3)
/ \
3 s s 4 ? == (\z -> f z 4)
/ \
4 ? <-
Ми тут бачимо, що s
просто "нагромаджує" послідовні часткові додатки f
, але y
в s x y = y . (\z -> f z x)
припускає, що при інтерпретації s 4 ?
(і, в свою чергу, всіх інших) не вистачає провідної функції, яка повинна бути складена з крайньою лівою лямбда.
Це просто наша ?
функція: час дати їй причину свого існування, окрім того, щоб зайняти місце у заклику до foldr
. Яким ми можемо його вибрати, щоб не змінювати отримані функції? Відповідь: id
, на одиничний елемент по відношенню до оператора композиції (.)
.
s s 1 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
/ \
1 s s 2 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
/ \
2 s s 3 (…) == id . (\z -> f z 4) . (\z -> f z 3)
/ \
3 s s 4 id == id . (\z -> f z 4)
/ \
4 id
Отже, шукана функція є
myFoldl f z xs = foldr (\x g a -> g (f a x)) id xs z
step = curry $ uncurry (&) <<< (flip f) *** (.)