Я только что сделал это упражнение и хотел бы поделиться, как я пришел к ответу (в основном то же, что и в вопросе, только буквы разные), в надежде, что он может быть кому-то полезен.
В качестве фона, начнем с чего foldLeft
и foldRight
сделать. Например, результат foldLeft в списке [1, 2, 3] с операцией *
и начальное значение z
это значение ((z * 1) * 2) * 3
Мы можем думать о foldLeft как потребление значений списка постепенно, слева направо , Другими словами, мы сначала начинаем со значения z
(что и было бы результатом, если список был пустым), затем мы показываем foldLeft
, что наш список начинается с 1, а значение становится z * 1
, затем foldLeft
видит, что наш список следующий имеет 2
и значение становится (z * 1) * 2
, и, наконец, после действия на 3, оно становится значением ((z * 1) * 2) * 3
.
1 2 3
Initially: z
After consuming 1: (z * 1)
After consuming 2: ((z * 1) * 2
After consuming 3: (((z * 1) * 2) * 3
Это конечное значение значение, которое мы хотим достичь, за исключением (как упражнение требует от нас), используя foldRight
вместо этого. Обратите внимание, что так же, как foldLeft
потребляет значения списка слева направо, foldRight
потребляет значения списка справа налево. Так в списке [1, 2, 3],
- Этот foldRight будет действовать 3 и [то], что дает [результат]
- Тогда он будет действовать на 2 и [результат], давая [result2]
- Наконец он будет действовать на 1 и [result2] дает окончательное выражение
- мы хотим, чтобы наше окончательное выражение будет
(((z * 1) * 2) * 3
другими словами: с помощью foldRight
, мы впервые прибыли в то, что результат будет, если список были пустыми, то результат, если список содержал только [3], тогда результат, если список был [2, 3], и, наконец, результат для списка [1, 2, 3].
То есть, это те ценности, которые мы хотели бы достичь, используя foldRight
:
1 2 3
Initially: z
After consuming 3: z * 3
After consuming 2: (z * 2) * 3
After consuming 1: ((z * 1) * 2) * 3
Таким образом, мы должны перейти от z
к (z * 3)
в (z * 2) * 3
к ((z * 1) * 2) * 3
.
Как значение, мы не можем сделать это: нет никакого естественного способа, чтобы перейти от значения (z * 3)
к значению (z * 2) * 3
, для произвольной операции *
. (Существует для умножения, как это коммутативное и ассоциативные, но мы используем только *
стоять произвольную операцию.)
Но функции мы можем быть в состоянии сделать это! Нам нужна функция с «заполнителем» или «дыркой»: что-то, что займет z
и положите его в нужное место.
- E.g. после первого шага (после действия на 3) мы имеем функцию заполнителя
z => (z * 3)
.Вернее, поскольку функция должна принимать произвольные значения, и мы использовали z
для определенного значения, давайте напишем это как t => (t * 3)
. (Эта функция, примененная на входе z
, дает значение (z * 3)
.)
- После второго шага (после действия на 2 и результата) у нас есть функция-заполнитель
t => (t * 2) * 3
?
Можем ли мы перейти от первой функции заполнителя к следующей? Пусть
f1(t) = t * 3
and f2(t) = (t * 2) * 3
Что с точки зрения f1
f2
?
f2(t) = f1(t * 2)
Да, мы можем! Таким образом, функция, которую мы хотим, принимает 2
и f1
и дает f2
. Назовем это g
. Мы имеем g(2, f1) = f2
где f2(t) = f1(t * 2)
или других слова
g(2, f1) =
t => f1(t * 2)
Давайте посмотрим, если это будет работать, если мы несли его вперед: следующий шаг будет g(1, f2) = (t => f2(t * 1))
и ОРЗ таких же, как t => f1((t * 1) * 2))
или t => (((t * 1) * 2) * 3)
.
Похоже, что это работает! И, наконец, применим к этому результату z
.
Каким должен быть первый шаг? Применим g
на 3
и f0
, чтобы получить f1
, где f1(t) = t * 3
, как определено выше, а также f1(t) = f0(t * 3)
из определения g
. Похоже, нам нужна функция f0
.
Давайте начнем заново.
Our foldLeft(List(1, 2, 3), z)(*) is ((z * 1) * 2) * 3
Types here: List(1, 2, 3) is type List[A]
z is of type B
* is of type (B, A) -> B
Result is of type B
We want to express that in terms of foldRight
As above:
f0 = identity. f0(t) = t.
f1 = g(3, f0). So f1(t) = f0(t * 3) = t * 3
f2 = g(2, f1). So f2(t) = f1(t * 2) = (t * 2) * 3
f3 = g(1, f2). So f3(t) = f2(t * 1) = ((t * 1) * 2) * 3
И, наконец, мы применяем f3 на z и получаем требуемое выражение. Все работает.Так
f3 = g(1, g(2, g(3, f0)))
что означает f3 = foldRight(xs, f0)(g)
Давайте определим g
, на этот раз вместо x * y
используя произвольную функцию s(x, y)
:
Собираем все это вместе
def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B = {
val f0 = (b: B) => b
def g(a: A, f: B=>B): B=>B =
t => f(s(t, a))
foldRight(xs, f0)(g)(z)
}
На этом уровне работает через книгу, я на самом деле предпочитают эту форму, поскольку она более ясна и понятна. Но чтобы приблизиться к форме решения, мы можем встраивать определения f0
и g
(мы больше не нужно объявлять тип g
, как это вход foldRight
и компилятор выводит его), что дает:
def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B =
foldRight(xs, (b: B) => b)((a, f) => t => f(s(t, a)))(z)
Это именно то, что находится в вопросе, просто с разными символами. Аналогично для foldRight с точки зрения foldLeft.
Это [упражнение] (http://book.realworldhaskell.org/read/functional-programming.html#Fold.hs:myFoldl) из книги RWH (Real World Haskell). В желтом блоке есть очень полезные комментарии, озаглавленные «Понимание foldl в терминах foldr». – folone
Действительно определение Haskell превосходит Scala на WTFs/s: '' 'myFoldl f z xs = foldr step id xs z где step x g a = g (f a x)' '' –
Есть ли ресурс с решениями для упражнений? – piotr