Перед тем как голосовать против, прочтите следующий абзац.
Я отправляю ответ для тех людей, которым этот подход может лучше соответствовать их образу мышления. Ответ, возможно, содержит избыточную информацию и мысли, но это то, что мне нужно для решения проблемы. Кроме того, поскольку это еще один ответ на тот же вопрос, очевидно, что он существенно пересекается с другими ответами, однако он рассказывает историю о том, как я мог понять эту концепцию.
На самом деле я начал записывать эти заметки как личную запись своих мыслей, пытаясь понять эту тему. Мне потребовался целый день, чтобы коснуться его сути, если я действительно понял.
Мой долгий путь к пониманию этого простого упражнения
Легкая часть: что нам нужно определить?
Что происходит в следующем примере вызова
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. Поскольку частичное применение fto 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, но yin 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) *** (.)