Функция Tailrec в Котлине

Я пытаюсь преобразовать фрагмент кода с хвостовой рекурсией, который использует Arrow Try, Someone и добиться того, что он делает функционально.

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

Вот исходная функция -

    private tailrec fun <T> eventuallyAux(
        startTime: LocalTime,
        timeout: Duration,
        retryInterval: Duration,
        function: () -> T
    ): T {

        val result = Try { function() }.toEither()
        return when (result) {
            is Either.Right -> result.b
            is Either.Left -> {
                throwEventuallyExceptionIfTimedOut(startTime, timeout, result.a)

                Thread.sleep(retryInterval.toMillis())
                eventuallyAux(startTime, timeout, retryInterval, function)
            }
        }
    }

Это то, что мне удалось очистить, используя Result в kotlin std-lib вместо toEither():

    private tailrec fun <T> eventuallyAux(
        startTime: LocalTime,
        timeout: Duration,
        retryInterval: Duration,
        function: () -> T
    ): T {

        return runCatching(function)
            .onSuccess { it.right() }
            .onFailure {
                throwEventuallyExceptionIfTimedOut(startTime, timeout, it)
                Thread.sleep(retryInterval.toMillis())
                eventuallyAux(startTime, timeout, retryInterval, function)
            }
    }

Компилятор жалуется, что значение возвращается с типом Result<T> вместо T. Как мне обойти это и вернуть T, если оценка прошла успешно, и передать throwable в функцию tailrec, если она не удалась?

Спасибо

Компилятор также жалуется на то, что функция помечена как tailrec, но не найден вызов tail. Разве последняя строка не рекурсивна?

Jonesh Sharma 22.11.2022 07:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
1
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

runCatching заключает возвращаемое значение в Result, поэтому тип возвращаемого значения — Result<T>. onSuccess и т. д. просто выполните действие над обернутым значением, но верните сам результат

Вот почему это не хвостовая рекурсия - для этого требуется, чтобы рекурсивный вызов был последним, что вы делаете в функции, поэтому там больше нечего делать, и текущий вызов функции может быть извлечен из стека. Не нужно возвращаться назад, когда там нечего делать! Результат можно просто передать тому, что находится перед ним в стеке. Это как удаление вызова и запуск нового, как итерация, вместо их вложения.

Но поскольку ваш код onFailure до того, как будет возвращен сам Result, в этом вызове все еще есть код, который нужно выполнить, нужно еще поработать. Таким образом, ваш рекурсивный вызов не является хвостовым вызовом - это не последнее, поэтому текущий вызов функции еще не может быть удален из стека вызовов. Таким образом, вы получаете обычную рекурсию, потому что ее нельзя оптимизировать.


К сожалению, вы не можете выполнять хвостовые вызовы внутри try/catch, потому что это не поддерживается. Вам нужно в основном воспроизвести подход в исходном коде, а именно:

  • вызовите функцию и получите Result
  • если это success, вернуть развернутое значение
  • в противном случае вернуть результат рекурсивного вызова

Таким образом, последнее, что вы делаете, это выполняет хвостовой вызов, и результат этого передается непосредственно обратно исходному вызывающему объекту. Вы должны быть в состоянии сделать это следующим образом:

private tailrec fun <T> eventuallyAux(
    startTime: LocalTime,
    timeout: Duration,
    retryInterval: Duration,
    function: () -> T
): T {
    val result = runCatching(function)
    return if (result.isSuccess) result.getOrThrow()
    else {
        throwEventuallyExceptionIfTimedOut(startTime, timeout, it)
        Thread.sleep(retryInterval.toMillis())
        // last thing that happens, nothing else to do but return the result of this
        eventuallyAux(startTime, timeout, retryInterval, function)
    }
}

Вы не можете использовать result.getOrElse, потому что блок else является лямбдой - я предполагаю, что это потому, что он преобразуется в объект Function и вызов, который не обрабатывается для оптимизации хвостовой рекурсии, но я не изучал это. Делая это таким образом, с самой простой структурой if/else, вы можете точно контролировать, что и когда происходит, и убедиться, что это оптимизировано.

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