Scala рекурсивная функция, которая возвращает другую функцию

Я смотрел на примеры техники каррирования в 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 возвращает, сколько функций и как будет выглядеть стек вызовов?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
259
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Давайте развернем рекурсию шаг за шагом.

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))
}

Спасибо за четкое и краткое объяснение. Мышление с помощью метода apply() очень полезно и помогло мне легче понять.

senko 11.04.2019 19:34

Конечно :) Но я должен сказать, что оба ваших ответа хорошо объяснены и понятны для меня.

senko 15.04.2019 21:22

Чтобы сделать его более понятным, я упрощу и уменьшу часть вашей функции.

Давайте сначала удалим параметр 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 почти одинаковы в создании.

senko 11.04.2019 19:38

да. (x:Int) => x + 1 — это просто синтаксический сахар для создания экземпляра класса. Насколько я знаю, лямбда-выражения в java работают так же.

Bogdan Vakulenko 11.04.2019 19:43

Во-первых, давайте уточним, что если вы запустите 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)

Другие вопросы по теме