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.