Почему Scala оптимизировать хвостовой вызов с помощью try/catch? - программирование
Подтвердить что ты не робот

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

Я смог взломать хвостовую рекурсивную альтернативу, но я все еще удивляюсь, почему это не оптимизировалось. Почему бы и нет?

4b9b3361

Ответ 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)
}