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





runCatching заключает возвращаемое значение в Result, поэтому тип возвращаемого значения — Result<T>. onSuccess и т. д. просто выполните действие над обернутым значением, но верните сам результат
Вот почему это не хвостовая рекурсия - для этого требуется, чтобы рекурсивный вызов был последним, что вы делаете в функции, поэтому там больше нечего делать, и текущий вызов функции может быть извлечен из стека. Не нужно возвращаться назад, когда там нечего делать! Результат можно просто передать тому, что находится перед ним в стеке. Это как удаление вызова и запуск нового, как итерация, вместо их вложения.
Но поскольку ваш код onFailure до того, как будет возвращен сам Result, в этом вызове все еще есть код, который нужно выполнить, нужно еще поработать. Таким образом, ваш рекурсивный вызов не является хвостовым вызовом - это не последнее, поэтому текущий вызов функции еще не может быть удален из стека вызовов. Таким образом, вы получаете обычную рекурсию, потому что ее нельзя оптимизировать.
К сожалению, вы не можете выполнять хвостовые вызовы внутри try/catch, потому что это не поддерживается. Вам нужно в основном воспроизвести подход в исходном коде, а именно:
Resultsuccess, вернуть развернутое значениеТаким образом, последнее, что вы делаете, это выполняет хвостовой вызов, и результат этого передается непосредственно обратно исходному вызывающему объекту. Вы должны быть в состоянии сделать это следующим образом:
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, вы можете точно контролировать, что и когда происходит, и убедиться, что это оптимизировано.
Компилятор также жалуется на то, что функция помечена как tailrec, но не найден вызов tail. Разве последняя строка не рекурсивна?