Kuyruk özyinelemeli işlevinin optimize edilmesini sağlamak için Scala ek açıklaması nedir?


98

@tailrecDerleyicinin bir kuyruk özyinelemeli işlevi optimize etmesini sağlamak için ek açıklama olduğunu düşünüyorum . Beyannamenin önüne mi koydunuz? Scala komut dosyası modunda kullanılırsa da çalışır mı (örneğin :load <file>REPL altında kullanarak )?

Yanıtlar:


119

" Kuyruk aramaları, @tailrec ve trambolinler " blog yayınından:

  • Scala 2.8'de, @tailrechangi yöntemlerin optimize edildiği hakkında bilgi almak için yeni açıklamayı da kullanabileceksiniz .
    Bu açıklama, derleyicinin optimize edeceğini umduğunuz belirli yöntemleri işaretlemenizi sağlar.
    Derleyici tarafından optimize edilmemişlerse bir uyarı alacaksınız.
  • Scala 2.7 veya önceki sürümlerde, bir yöntemin optimize edilip edilmediğini anlamak için manuel teste veya bayt kodunun incelenmesine güvenmeniz gerekecektir.

Misal:

@tailrecDeğişikliklerinizin işe yaradığından emin olmak için bir açıklama ekleyebilirsiniz .

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)
  }
}

Ve REPL'den çalışır ( Scala REPL ipuçları ve püf noktalarından örnek ):

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 = {
                    ^

44

Scala derleyicisi, herhangi bir gerçek kuyruk özyinelemeli yöntemi otomatik olarak optimize eder. Kuyruk özyinelemeli olduğunu düşündüğünüz bir yöntemi açıklama ile açıklama eklerseniz @tailrec, o zaman derleyici, yöntem aslında kuyruk özyinelemeli değilse sizi uyaracaktır. Bu @tailrec, hem bir yöntemin şu anda optimize edilebilir olmasını hem de değiştirilirken optimize edilebilir kalmasını sağlamak için ek açıklamayı iyi bir fikir haline getirir .

Scala, geçersiz kılınabilirse, bir yöntemi kuyruk özyinelemeli olarak değerlendirmez. Bu nedenle, yöntem özel, nihai, bir nesne üzerinde (bir sınıf veya özelliğin aksine) veya optimize edilecek başka bir yöntemin içinde olmalıdır.


8
Sanırım bu, overrideJava'daki ek açıklamaya benziyor - kod onsuz çalışır, ancak oraya koyarsanız size bir hata yapıp yapmadığınızı söyler.
Zoltán

23

Ek açıklama scala.annotation.tailrec. Yöntem kuyruk çağrısı optimize edilemiyorsa bir derleyici hatasını tetikler, bu şu durumlarda gerçekleşir:

  1. Özyinelemeli çağrı kuyruk konumunda değil
  2. Yöntem geçersiz kılınabilir
  3. Yöntem nihai değil (önceki özel durum)

defBir yöntem tanımının hemen önüne yerleştirilir . REPL'de çalışır.

Burada ek açıklamayı içe aktarıyoruz ve bir yöntemi olarak işaretlemeye çalışıyoruz @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! Son çağrı 1.+(), değil length()! Yöntemi yeniden formüle edelim:

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

length0Başka bir yöntem kapsamında tanımlandığı için bunun otomatik olarak özel olduğunu unutmayın .


2
Yukarıda söylediklerinizi genişleten Scala, kuyruk çağrılarını yalnızca tek bir yöntem için optimize edebilir. Karşılıklı özyinelemeli çağrılar optimize edilmeyecektir.
Rich Dougherty

Nit seçici olmaktan nefret ediyorum, ancak Nil durumunda örneğinizde doğru bir liste uzunluğu işlevi için sayım yapmalısınız, aksi takdirde özyineleme bittiğinde dönüş değeri olarak her zaman 0 alırsınız.
Lucian Enache
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.