คำอธิบายประกอบ Scala คืออะไรเพื่อให้แน่ใจว่าฟังก์ชันการเรียกซ้ำหางได้รับการปรับให้เหมาะสม


98

ฉันคิดว่ามี@tailrecคำอธิบายประกอบเพื่อให้แน่ใจว่าคอมไพเลอร์จะปรับแต่งฟังก์ชันหางซ้ำให้เหมาะสม คุณเอาไว้หน้าใบประกาศหรือไม่? ยังใช้งานได้หรือไม่หากใช้ Scala ในโหมดสคริปต์ (เช่นใช้:load <file>ภายใต้ REPL)

คำตอบ:


119

จากบล็อกโพสต์" Tail Calling, @tailrec และ trampolines ":

  • ใน Scala 2.8 คุณจะสามารถใช้@tailrecคำอธิบายประกอบใหม่เพื่อรับข้อมูลเกี่ยวกับวิธีการที่เหมาะสมที่สุด
    คำอธิบายประกอบนี้ให้คุณทำเครื่องหมายวิธีการเฉพาะที่คุณหวังว่าคอมไพเลอร์จะปรับให้เหมาะสม
    จากนั้นคุณจะได้รับคำเตือนหากคอมไพเลอร์ไม่ได้รับการปรับให้เหมาะสม
  • ใน Scala 2.7 หรือก่อนหน้าคุณจะต้องพึ่งพาการทดสอบด้วยตนเองหรือการตรวจสอบ bytecode เพื่อดูว่ามีการปรับวิธีการให้เหมาะสมหรือไม่

ตัวอย่าง:

คุณสามารถเพิ่ม@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

คอมไพเลอร์ Scala จะปรับแต่งวิธีการเรียกซ้ำหางอย่างแท้จริงโดยอัตโนมัติ หากคุณใส่คำอธิบายประกอบวิธีที่คุณเชื่อว่าเป็นหางซ้ำพร้อมกับ@tailrecคำอธิบายประกอบคอมไพลเลอร์จะเตือนคุณหากเมธอดนั้นไม่ใช่หางซ้ำ สิ่งนี้ทำให้@tailrecคำอธิบายประกอบเป็นความคิดที่ดีทั้งเพื่อให้แน่ใจว่าวิธีการนั้นได้รับการปรับให้เหมาะสมในปัจจุบันและยังคงปรับให้เหมาะสมได้เมื่อมีการแก้ไข

โปรดทราบว่า Scala ไม่ถือว่าเป็นวิธีที่จะเรียกซ้ำหางหากสามารถแทนที่ได้ ดังนั้นวิธีการต้องเป็นแบบส่วนตัวขั้นสุดท้ายบนวัตถุ (ตรงข้ามกับคลาสหรือลักษณะ) หรือภายในวิธีการอื่นที่จะปรับให้เหมาะสม


8
ฉันคิดว่านี่เป็นเหมือนoverrideคำอธิบายประกอบใน Java - รหัสจะทำงานโดยไม่มีมัน แต่ถ้าคุณใส่ไว้ที่นั่นมันจะบอกคุณว่าคุณทำผิดหรือไม่
Zoltán

23

scala.annotation.tailrecคำอธิบายประกอบคือ จะทริกเกอร์ข้อผิดพลาดของคอมไพเลอร์หากวิธีการไม่สามารถปรับการโทรหางให้เหมาะสมได้ซึ่งจะเกิดขึ้นหาก:

  1. การเรียกแบบเรียกซ้ำไม่อยู่ในตำแหน่งหาง
  2. วิธีนี้อาจถูกลบล้างได้
  3. วิธีนี้ยังไม่สิ้นสุด (กรณีพิเศษก่อนหน้านี้)

มันถูกวางไว้ก่อนหน้าไฟล์ defนิยามวิธีการ มันทำงานใน REPL

@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
การขยายสิ่งที่คุณได้กล่าวไว้ข้างต้น Scala สามารถเพิ่มประสิทธิภาพการโทรหางสำหรับวิธีการเดียวเท่านั้น การโทรซ้ำร่วมกันจะไม่ได้รับการปรับให้เหมาะสม
Rich Dougherty

ฉันเกลียดการเป็นคนที่จู้จี้จุกจิก แต่ในตัวอย่างของคุณในกรณี Nil คุณควรคืนค่าฟังก์ชันความยาวรายการที่ถูกต้องมิฉะนั้นคุณจะได้ 0 เป็นค่าส่งคืนเสมอเมื่อการเรียกซ้ำเสร็จสิ้น
Lucian Enache
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.