꼬리 재귀 함수를 최적화하기위한 Scala 주석은 무엇입니까?


98

@tailrec컴파일러가 꼬리 재귀 함수를 최적화 할 수 있도록 주석 이 있다고 생각 합니다. 선언문 앞에 그냥 두세요? Scala가 스크립팅 모드 (예 :load <file>: REPL 에서 사용)에서 사용되는 경우에도 작동합니까 ?

답변:


119

" Tail calls, @tailrec 및 trampolines "블로그 게시물에서 :

  • Scala 2.8에서는 새로운 @tailrec주석을 사용하여 최적화 된 메서드에 대한 정보를 얻을 수도 있습니다 .
    이 주석을 사용하면 컴파일러가 최적화 할 특정 메서드를 표시 할 수 있습니다.
    그런 다음 컴파일러에 의해 최적화되지 않은 경우 경고가 표시됩니다.
  • Scala 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

Scala 컴파일러는 진정한 꼬리 재귀 방법을 자동으로 최적화합니다. 꼬리 재귀 적이라고 생각되는 메서드에 주석을 달면 @tailrec컴파일러는 메서드가 실제로 꼬리 재귀 적이 지 않은지 경고합니다. 이렇게하면 @tailrec메서드가 현재 최적화 가능하고 수정 될 때도 최적화 가능한 상태로 유지되도록하는 주석이 좋은 아이디어가됩니다.

Scala는 재정의 할 수있는 경우 메서드를 꼬리 재귀로 간주하지 않습니다. 따라서 메서드는 개인, 최종, 객체 (클래스 또는 특성과 반대) 또는 최적화 할 다른 메서드 내부에 있어야합니다.


8
나는 이것이 overrideJava 의 주석 과 비슷 하다고 생각합니다. 코드는 코드 없이도 작동하지만 거기에 넣으면 실수를했는지 알려줍니다.
Zoltán 2015 년

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 17.01.15
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.