Почему Scala оптимизировать хвостовой вызов с помощью try/catch?
В qaru.site/info/121900/... я дал следующий рекурсивный код:
def retry[T](n: Int)(fn: => T): T = {
try {
fn
} catch {
case e if n > 1 =>
retry(n - 1)(fn)
}
}
Если добавить аннотацию @tailrec
, я получаю:
Не удалось оптимизировать повтор метода @tailrec annotated method: он содержит рекурсивный вызов не в хвостовом положении.
Я смог взломать хвостовую рекурсивную альтернативу, но я все еще удивляюсь, почему это не оптимизировалось. Почему бы и нет?
Ответы
Ответ 1
Чтобы оптимизировать хвостовую рекурсию, это должно быть преобразовано в следующее:
def retry[T](n: Int)(fn: => T): T = {
START:
try {
fn
} catch {
case e if n > 1 =>
n = n - 1
GOTO START
}
}
Когда он выполняет цикл GOTO
, он должен выйти в область действия блока catch
. Но в исходной рекурсивной версии выполнение рекурсивного вызова все еще находится в блоке catch
. Если язык позволяет, что это может когда-либо изменить смысл кода, то это не будет правильной оптимизацией.
РЕДАКТИРОВАТЬ: Из обсуждения с Рексом Керром в комментариях это преобразование, сохраняющее поведение в Scala (но только тогда, когда нет finally
). Таким образом, очевидно, что компилятор Scala еще не распознает, что последний вызов блока catch
, где нет finally
, находится в позиции хвостового вызова.
Ответ 2
Я не думаю, что список реализованных преобразований для размещения кода в хвостовой рекурсивной форме включает в себя перемещение блоков обработки исключений. Это особенно сложно, даже если вы можете придумать примеры (как есть) того, где это должно быть законным. (Существует много случаев, которые выглядят законными, а не (например, если есть блок finally
) или требуют значительно более сложных правил обмотки/разматывания.)
Ответ 3
Я нашел это решение в другом месте на SO. В принципе, используйте return
с fn
, чтобы, если fn
возвращает исключение без исключения, ваша функция также будет работать. Если fn
выполняет команду throw, а исключение удовлетворяет условию n > 1
, тогда исключение игнорируется и рекурсивный вызов происходит после блока catch.
@tailrec
def retry[T](n: Int)(fn: => T): T = {
try {
return fn
} catch {
case e if n > 1 => // fall through to retry below
}
retry(n - 1)(fn)
}