Я смотрел на примеры техники каррирования в scala и не понимал, как функция может возвращать другую функцию, если функция рекурсивная.
например я понимаю этот код
def addOne(x: Int): Int = x => x + 1
def repeater(myFunc: Int => Int, n: Int, x:Int): Int =
if (n<=0) x
else repeater(myFunc, n-1, myFunc(x))
для меня нормально, если я скажу, что Repeater(addOne, 10, 1) возвращает 11. Пока n не станет равным нулю, я уменьшаю n на 1, и стек рекурсии начинает работать снизу вверх.
Но этот меня смущает
def repeater(myFunc: Int => Int, n:Int): Int => Int = {
if (n<=0) (x:Int) => x
else (x: Int) => repeater(myFunc, n-1)(myFunc(x))
}
Дело в том, что я не понимаю здесь, например, если я запускаю повторитель (addOne, 2), он будет выглядеть как другая часть, а еще часть говорит о возврате функции, поэтому программа сначала возвращает функцию или выполняет повторитель еще один раз с n-1 (1-1) и вернуть другую функцию? Эта часть else возвращает, сколько функций и как будет выглядеть стек вызовов?
Давайте развернем рекурсию шаг за шагом.
repeater(addOne, 2)
возвращает новую анонимную функцию
(x:Int) => repeater(addOne, 1).apply(addOne(x))
Где repeater(addOne, 1)
возвращает новую анонимную функцию
(x:Int) => repeater(addOne, 0).apply(addOne(x))
Где repeater(addOne, 0)
возвращает новую анонимную функцию
(x:Int) => x
Поэтому repeater(addOne, 1)
возвращает анонимную функцию, которая выглядит как
(y:Int) => {
((x: Int) => {
x
}).apply(addOne(y))
}
И repeater(addOne, 2)
возвращает анонимную функцию, которая выглядит как
(z:Int) => {
((y: Int) => {
((x: Int) => {
x
}).apply(addOne(y))
}).apply(addOne(z))
}
Конечно :) Но я должен сказать, что оба ваших ответа хорошо объяснены и понятны для меня.
Чтобы сделать его более понятным, я упрощу и уменьшу часть вашей функции.
Давайте сначала удалим параметр myFunc
и заменим его функцией addOne
, что напрямую уменьшит сложность, сохраняя при этом основной вопрос:
def repeater(n:Int): Int => Int = {
if (n<=0) (x:Int) => x
else (x: Int) => repeater(n-1)(addOne(x))
}
Теперь давайте добавим литеральный код инстанцирования функции desugar.
def repeater(n:Int): Int => Int = {
if (n<=0) new Function1[Int,Int]{ //#1
override def apply(x: Int): Int = x
}
else new Function1[Int,Int]{ //#2
override def apply(x: Int): Int = repeater(n-1)(addOne(x))
}
}
Итак, теперь вы можете видеть, что когда вы вызываете reporter(2)
, он создает новую функцию #2
без ее оценки. Итак, в результате у вас будет что-то вроде этого:
val repeaterFirstIteration: Int => Int = {
new Function1[Int,Int]{
override def apply(x: Int): Int = repeater(2-1)(addOne(x))
}
}
или давайте подсластить его обратно
val repeaterFirstIteration: Int => Int = {
x => repeater(2-1)(addOne(x))
}
так что теперь у вас есть val
содержащий литерал функции, который вы можете вызывать вот так repeaterFirstIteration(...)
Спасибо, лямбда-выражение, такое как (x:Int) => x + 1
, и создание функции (на самом деле экземпляра класса) с помощью Function1 абсолютно одинаковы, верно? (Как в Java и функциональных интерфейсах.) Насколько я понимаю, функции scala и java почти одинаковы в создании.
да. (x:Int) => x + 1
— это просто синтаксический сахар для создания экземпляра класса. Насколько я знаю, лямбда-выражения в java работают так же.
Во-первых, давайте уточним, что если вы запустите repeater(addOne, 3)
, вы вернете только функцию. Вам нужно будет запуститьrepeater(addOne, 3)(SomeOtherInteger)
, чтобы получить целочисленное значение и результат ваших вычислений. Все, что делает здесь рекурсия, — это строит функцию. repeater(addOne, 3)
возвращает функцию, которая принимает целое число и возвращает целое число. Возьмем пример repeater(addOne, 3)
, если мы полностью выпишем результат рекурсии, вот что мы получим
{ x => {x => { x => { x => x }(myFunc(x)) }(myFunc(x)) }(myFunc(x))) }
Это может показаться немного запутанным, но давайте разберемся.
Давайте сосредоточимся на самой внутренней части - { x => x }(myFunc(x))
. Это можно разделить на две части: функцию и вход в функцию. Функция — это { x => x }
, а вход этой функции — (myFunc(x))
. В этом случае все, что делает функция, — возвращает ввод обратно пользователю. Таким образом, если мы напишем { x => x }(1)
, мы получим 1
. Таким образом, мы можем заменить все { x => x }(myFunc(x))
только на myFunc(x)
. То, что у нас осталось, это
{ x => { x => { x => myFunc(x) }(myFunc(x)) }(myFunc(x)) }
Давайте посмотрим на термин { x => myFunc(x)}(myFunc(x))
. Здесь функциональной частью является { x => myFunc(x) }
, а вход в эту функциональную часть задается (myFunc(x))
. Функциональная часть принимает целое число x
и применяет myFunc
к этому целому числу. По сути, это то же самое, что применить myFunc
непосредственно к этому целому числу. В этом случае целое число, к которому мы его применяем, является вводом myFunc(x)
, поэтому мы можем переписать { x => myFunc(x) }(myFunc(x))
как myFunc(myFunc(x))
. Теперь у нас есть
{ x => { x => myFunc(myFunc(x)) }(myFunc(x)) }
Мы можем применить ту же логику, что и на предыдущем шаге, чтобы разбить термин { x => myFunc(myFunc(x)) }(myFunc(x))
. Мы бы получили myFunc(myFunc(myFunc(x)))
. Если вы продолжите эту логику, вы увидите, что repeater
будет продолжать сочинять myFunc
. Для каждого n
он добавит еще один слой myFunc
. Результат этого будет выглядеть так, если n = 3
{ x => myFunc(myFunc(myFunc((x))) }
Таким образом, для repeater(addOne, 3)
мы получим
{ x => addOne(addOne(addOne(x))) }
И repeater(addOne, 5)
есть
{ x => addOne(addOne(addOne(addOne(addOne(x))))) }
Все, что repeater
делает, это просто создает эту функцию и возвращает ее пользователю. Вы можете взять эту функцию возврата repeater
и поместить val
с именем f
val f = { x => addOne(addOne(addOne(x))) } //ie repeater(addOne, 3)
f
принимает целое число и возвращает это целое число плюс 3. Затем мы можем получить фактический результат, который мы хотим, используя это
val someIntegerPlus3: Int = f(someInteger)
Спасибо за четкое и краткое объяснение. Мышление с помощью метода apply() очень полезно и помогло мне легче понять.