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