Chú thích Scala là gì để đảm bảo một hàm đệ quy đuôi được tối ưu hóa?


98

Tôi nghĩ rằng có một @tailrecchú thích để đảm bảo trình biên dịch sẽ tối ưu hóa một hàm đệ quy đuôi. Bạn chỉ cần đặt nó ở phía trước của tờ khai? Nó cũng hoạt động nếu Scala được sử dụng trong chế độ kịch bản (ví dụ: sử dụng :load <file>trong REPL)?

Câu trả lời:


119

Từ bài đăng trên blog " Đuôi gọi, @tailrec và trampolines ":

  • Trong Scala 2.8, bạn cũng sẽ có thể sử dụng @tailrecchú thích mới để nhận thông tin về các phương pháp được tối ưu hóa.
    Chú thích này cho phép bạn đánh dấu các phương pháp cụ thể mà bạn hy vọng trình biên dịch sẽ tối ưu hóa.
    Sau đó, bạn sẽ nhận được cảnh báo nếu chúng không được trình biên dịch tối ưu hóa.
  • Trong Scala 2.7 hoặc phiên bản cũ hơn, bạn sẽ cần phải dựa vào kiểm tra thủ công hoặc kiểm tra mã bytecode, để tìm ra phương pháp đã được tối ưu hóa hay chưa.

Thí dụ:

bạn có thể thêm @tailrecchú thích để có thể chắc chắn rằng các thay đổi của mình đã hoạt động.

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

Và nó hoạt động từ REPL (ví dụ từ các mẹo và thủ thuật 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

Trình biên dịch Scala sẽ tự động tối ưu hóa bất kỳ phương thức đệ quy đuôi thực sự nào. Nếu bạn chú thích một phương thức mà bạn tin là đệ quy đuôi với @tailrecchú thích, thì trình biên dịch sẽ cảnh báo bạn nếu phương thức thực sự không đệ quy đuôi. Điều này làm cho @tailrecchú thích trở thành một ý tưởng tốt, vừa để đảm bảo rằng một phương pháp hiện có thể được tối ưu hóa và nó vẫn có thể tối ưu hóa khi nó được sửa đổi.

Lưu ý rằng Scala không coi một phương thức là đệ quy đuôi nếu nó có thể bị ghi đè. Do đó, phương thức phải là private, cuối cùng, trên một đối tượng (trái ngược với một lớp hoặc đặc điểm), hoặc bên trong một phương thức khác để được tối ưu hóa.


8
Tôi cho rằng điều này giống như overridechú thích trong Java - mã hoạt động mà không cần nó, nhưng nếu bạn đặt nó ở đó, nó sẽ cho bạn biết nếu bạn mắc lỗi.
Zoltán

23

Chú thích là scala.annotation.tailrec. Nó gây ra lỗi trình biên dịch nếu phương thức không thể được tối ưu hóa cuộc gọi đuôi, điều này xảy ra nếu:

  1. Lời gọi đệ quy không ở vị trí đuôi
  2. Phương thức có thể bị ghi đè
  3. Phương thức không phải là cuối cùng (trường hợp đặc biệt của phần trước)

Nó được đặt ngay trước defđịnh nghĩa phương thức trong. Nó hoạt động trong REPL.

Ở đây chúng tôi nhập chú thích và cố gắng đánh dấu một phương thức là @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 { 
                    ^

Giáo sư! Lời kêu gọi cuối cùng là 1.+(), không phải length()! Hãy định dạng lại phương pháp:

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

Lưu ý rằng length0nó tự động là private vì nó được định nghĩa trong phạm vi của một phương thức khác.


2
Mở rộng những gì bạn đã nói ở trên, Scala chỉ có thể tối ưu hóa các lệnh gọi đuôi cho một phương pháp duy nhất. Các cuộc gọi đệ quy lẫn nhau sẽ không được tối ưu hóa.
Rich Dougherty

Tôi không thích là người kén chọn nit, nhưng trong ví dụ của bạn trong trường hợp Nil, bạn nên trả về tally cho một hàm độ dài danh sách chính xác, nếu không bạn sẽ luôn nhận được 0 làm giá trị trả về khi quá trình đệ quy kết thúc.
Lucian Enache
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.