Как обеспечить постоянную рекурсию хвоста

Вопрос:

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

Образец кода:

import scala.util.Try
import scala.annotation.tailrec

object Main {
val trials = 10

@tailrec
val gcd : (Int, Int) => Int = {
case (a,b) if (a == b) => a
case (a,b) if (a > b) => gcd (a-b,b)
case (a,b) if (b > a) => gcd (a, b-a)
}

def main(args : Array[String]) : Unit = {
testTailRec()
}

def testTailRec() {
val outputs : List[Boolean] = Range(0, trials).toList.map(_ + 6000) map { x =>
Try( gcd(x, 1) ).toOption.isDefined
}
outputTestResult(outputs)
}

def outputTestResult(source : List[Boolean]) = {
val failed = source.count(_ == false)
val initial = source.takeWhile(_ == false).length
println( s"totally $failed failures, $initial of which at the beginning")
}
}

Запуск будет производить следующий вывод:

[info] Running Main
[info] totally 2 failures, 2 of which at the beginning

Таким образом, первые два прогона выполняются без оптимизации и отбрасываются на полпути из-за исключения stackoveflow, и только последующие вызовы создают желаемый результат.

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

Обновить:

Мне сказали использовать двухэтапное определение

@tailrec
def gcd_worker(a: Int, b: Int): Int = {
if (a == b) a
else if (a > b) gcd(a-b,b)
else gcd(a, b-a)
}
val gcd : (Int,Int) => Int = gcd_worker(_,_)

Я предпочитаю сохранять чистое определение функционального стиля, если это возможно.

Лучший ответ:

Я не думаю, что @tailrec применяется к функции, определенной как val. Измените его на def и он будет работать без ошибок.

Ответ №1

Из того, что я понимаю, @tailrec [1] должен быть на методе, а не на поле. Я смог заставить это быть хвостом рекурсивным в REPL, сделав следующее изменение:

@tailrec
def gcd(a: Int, b: Int): Int = {
if (a == b) a
else if (a > b) gcd(a-b,b)
else gcd(a, b-a)
}

[1] http://www.scala-lang.org/api/current/index.html#scala.annotation.tailrec

Оцените статью
TechArks.Ru
Добавить комментарий