web-dev-qa-db-fra.com

Quelle est l'annotation Scala pour garantir une fonction récursive de queue optimisée?

Je pense qu'il y a @tailrec annotation pour garantir que le compilateur optimisera une fonction récursive de queue. Le mettez-vous juste devant la déclaration? Cela fonctionne-t-il également si Scala est utilisé en mode script (par exemple en utilisant :load <file> sous REPL)?

90
huynhjl

Extrait du blog " Tail calls, @tailrec and trampolines ":

  • Dans Scala 2.8, vous pourrez également utiliser le nouveau @tailrec annotation pour obtenir des informations sur les méthodes optimisées.
    Cette annotation vous permet de marquer des méthodes spécifiques que vous espérez que le compilateur optimisera.
    Vous obtiendrez alors un avertissement s'ils ne sont pas optimisés par le compilateur.
  • Dans Scala 2.7 ou version antérieure, vous devrez vous fier à des tests manuels ou à l'inspection du bytecode pour déterminer si une méthode a été optimisée.

Exemple:

vous pouvez ajouter un @tailrec annotation pour vous assurer que vos modifications ont fonctionné.

import scala.annotation.tailrec

class Factorial2 {
  def factorial(n: Int): Int = {
    @tailrec def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) acc
      else factorialAcc(n * acc, n - 1)
    }
    factorialAcc(1, n)
  }
}

Et cela fonctionne à partir du REPL (exemple du Scala REPL trucs et astuces ):

C:\Prog\Scala\tests>scala
Welcome to Scala version 2.8.0.RC5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scala.annotation.tailrec
import scala.annotation.tailrec

scala> class Tails {
     | @tailrec def boom(x: Int): Int = {
     | if (x == 0) throw new Exception("boom!")
     | else boom(x-1)+ 1
     | }
     | @tailrec def bang(x: Int): Int = {
     | if (x == 0) throw new Exception("bang!")
     | else bang(x-1)
     | }
     | }
<console>:9: error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position
       @tailrec def boom(x: Int): Int = {
                    ^
<console>:13: error: could not optimize @tailrec annotated method: it is neither private nor final so can be overridden
       @tailrec def bang(x: Int): Int = {
                    ^
111
VonC

Le compilateur Scala optimisera automatiquement toute méthode véritablement récursive de queue. Si vous annotez une méthode que vous pensez être récursive de queue avec le @tailrec annotation, le compilateur vous avertira si la méthode n'est en fait pas récursive. Cela rend le @tailrec l'annotation est une bonne idée, à la fois pour garantir qu'une méthode est actuellement optimisable et qu'elle reste optimisable au fur et à mesure de sa modification.

Notez que Scala ne considère pas qu'une méthode est récursive si elle peut être remplacée. Ainsi, la méthode doit être soit privée, finale, sur un objet (par opposition à une classe ou un trait) ou à l'intérieur d'une autre méthode à optimiser.

38
Dave Griffith

L'annotation est scala.annotation.tailrec. Il déclenche une erreur de compilation si la méthode ne peut pas être optimisée pour les appels de queue, ce qui se produit si:

  1. L'appel récursif n'est pas en position de queue
  2. La méthode pourrait être annulée
  3. La méthode n'est pas définitive (cas particulier du précédent)

Il est placé juste avant le def dans une définition de méthode. Cela fonctionne dans le REPL.

Ici, nous importons l'annotation et essayons de marquer une méthode comme @tailrec.

scala> import annotation.tailrec
import annotation.tailrec

scala> @tailrec def length(as: List[_]): Int = as match {  
     |   case Nil => 0
     |   case head :: tail => 1 + length(tail)
     | }
<console>:7: error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position
       @tailrec def length(as: List[_]): Int = as match { 
                    ^

Oops! La dernière invocation est 1.+(), pas length()! Reformulons la méthode:

scala> def length(as: List[_]): Int = {                                
     |   @tailrec def length0(as: List[_], tally: Int = 0): Int = as match {
     |     case Nil          => tally                                       
     |     case head :: tail => length0(tail, tally + 1)                    
     |   }                                                                  
     |   length0(as)
     | }
length: (as: List[_])Int

Notez que length0 Est automatiquement privé car il est défini dans le cadre d'une autre méthode.

23
retronym