पूंछ पुनरावर्ती कार्य को अनुकूलित करने के लिए स्केल एनोटेशन क्या है?


98

मुझे लगता है कि यह @tailrecसुनिश्चित करने के लिए एनोटेशन है कि संकलक एक पूंछ पुनरावर्ती फ़ंक्शन का अनुकूलन करेगा। क्या आपने इसे घोषणा के सामने रखा है? क्या यह भी काम करता है अगर स्कैल का उपयोग स्क्रिप्टिंग मोड में किया जाता है (उदाहरण के लिए :load <file>REPL के तहत उपयोग किया जाता है)?

जवाबों:


119

" टेल कॉल, @ वर्क्रेक और ट्रम्पोलिंस " ब्लॉग पोस्ट से:

  • स्केल 2.8 में, आप नए @tailrecएनोटेशन का उपयोग करने के बारे में जानकारी प्राप्त करने में सक्षम होंगे कि कौन से तरीके अनुकूलित हैं।
    यह एनोटेशन आपको विशिष्ट तरीकों को चिह्नित करने देता है, जो आपको उम्मीद है कि कंपाइलर अनुकूलन करेगा।
    यदि आप संकलक द्वारा अनुकूलित नहीं किए जाते हैं तो आपको एक चेतावनी मिलेगी।
  • स्केल 2.7 या इससे पहले, आपको मैन्युअल परीक्षण, या बायटेकोड के निरीक्षण पर भरोसा करने की आवश्यकता होगी, ताकि यह पता लगाया जा सके कि कोई विधि अनुकूलित की गई है या नहीं।

उदाहरण:

आप एक @tailrecएनोटेशन जोड़ सकते हैं ताकि आप सुनिश्चित कर सकें कि आपके परिवर्तनों ने काम किया है।

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

और यह REPL से काम करता है ( Scala REPL टिप्स और ट्रिक्स से उदाहरण ):

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

स्काला कंपाइलर स्वचालित रूप से किसी भी वास्तव में पूंछ-पुनरावर्ती विधि का अनुकूलन करेगा। यदि आप एक विधि का @tailrecएनोटेशन करते हैं जो आपको लगता है कि एनोटेशन के साथ पूंछ-पुनरावर्ती है , तो संकलक आपको चेतावनी देगा यदि विधि वास्तव में पूंछ-पुनरावृत्ति नहीं है। यह @tailrecएनोटेशन को एक अच्छा विचार बनाता है, दोनों यह सुनिश्चित करने के लिए कि एक विधि वर्तमान में अनुकूलन योग्य है और यह संशोधित होने के साथ ही अनुकूलन योग्य है।

ध्यान दें कि स्केला एक विधि को पूंछ-पुनरावर्ती नहीं मानती है यदि इसे ओवरराइड किया जा सकता है। इस प्रकार विधि या तो निजी होनी चाहिए, एक वस्तु पर (अंतिम रूप से, एक वर्ग या विशेषता के विपरीत), या दूसरी विधि के अंदर अनुकूलित होने के लिए।


8
मुझे लगता है कि यह overrideजावा में एनोटेशन की तरह है - कोड इसके बिना काम करता है, लेकिन अगर आप इसे वहां डालते हैं तो यह बताता है कि क्या आपने गलती की है।
ज़ोल्टन 15

23

एनोटेशन है scala.annotation.tailrec। यह संकलक त्रुटि को ट्रिगर करता है यदि विधि टेल कॉल को अनुकूलित नहीं किया जा सकता है, जो कि होता है:

  1. पुनरावर्ती कॉल पूंछ की स्थिति में नहीं है
  2. विधि को ओवरराइड किया जा सकता है
  3. विधि अंतिम नहीं है (पूर्ववर्ती का विशेष मामला)

इसे defएक विधि परिभाषा के ठीक पहले रखा गया है । यह आरईपीएल में काम करता है।

यहां हम एनोटेशन आयात करते हैं, और एक विधि को चिह्नित करने का प्रयास करते हैं @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 { 
                    ^

ऊप्स! अंतिम आह्वान है 1.+(), नहीं length()! आइए विधि में सुधार करें:

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

ध्यान दें कि length0यह स्वचालित रूप से निजी है क्योंकि इसे किसी अन्य विधि के दायरे में परिभाषित किया गया है।


2
ऊपर आपने जो कहा है, उस पर विस्तार करते हुए, स्काला केवल एकल विधि के लिए टेल कॉल का अनुकूलन कर सकती है। पारस्परिक रूप से पुनरावर्ती कॉल को अनुकूलित नहीं किया जाएगा।
रिच डफर्टी

मैं नट पिकी एक होने से नफरत करता हूं, लेकिन निल मामले में आपके उदाहरण में आपको एक सही सूची लंबाई फ़ंक्शन के लिए टैली वापस करना चाहिए अन्यथा आपको रिकर्स समाप्त होने पर रिटर्न मान के रूप में हमेशा 0 मिलेगा।
लुसियन एनचे
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.