복붙노트

[SCALA] 방법은 최종 않는 이유는 스칼라 컴파일러는 꼬리 호출 최적화를 적용하지 않습니다?

SCALA

방법은 최종 않는 이유는 스칼라 컴파일러는 꼬리 호출 최적화를 적용하지 않습니다?

방법은 최종 않는 이유는 스칼라 컴파일러는 꼬리 호출 최적화를 적용하지 않습니다?

예를 들어,이 :

class C {
    @tailrec def fact(n: Int, result: Int): Int =
        if(n == 0)
            result
        else
            fact(n - 1, n * result)
}

결과

컴파일러는 이와 같은 경우에 총 소유 비용 (TCO)을 적용하면 잘못된 정확히 어떻게 갈 것인가?

해결법

  1. ==============================

    1.REPL 다음과 같은 상호 작용을 고려하십시오. 먼저 우리는 계승 방법으로 클래스를 정의 :

    REPL 다음과 같은 상호 작용을 고려하십시오. 먼저 우리는 계승 방법으로 클래스를 정의 :

    scala> class C {
             def fact(n: Int, result: Int): Int =
               if(n == 0) result
               else fact(n - 1, n * result)
           }
    defined class C
    
    scala> (new C).fact(5, 1)
    res11: Int = 120
    

    이제 슈퍼 클래스의 답변을 두 배로 서브 클래스에서 재정의하자 :

    scala> class C2 extends C {
             override def fact(n: Int, result: Int): Int = 2 * super.fact(n, result)
           }
    defined class C2
    
    scala> (new C).fact(5, 1)
    res12: Int = 120
    
    scala> (new C2).fact(5, 1)
    

    이 마지막 호출을 위해 무엇을 기대하는 결과는? 당신은 (240) 그러나 전혀 기대되지 않을 수 있습니다 :

    scala> (new C2).fact(5, 1)
    res13: Int = 7680
    

    슈퍼 클래스의 방법은 재귀 호출을 할 때, 재귀 호출이 서브 클래스를 통과하기 때문이다.

    (240)는 정답이되도록 일을 재정의하는 경우 꼬리 호출 최적화 여기에 슈퍼 클래스에서 수행하는, 그것은 안전 할 것입니다. 스칼라 (또는 Java)이 어떻게 작동하는지하지만 그게 아니다.

    메소드가 마지막으로 표시되지 않는 한이 재귀 호출을 할 때, 그 자체를 호출되지 않을 수 있습니다.

    방법은 최종 (또는 개인)가 아닌 @tailrec 일을하지 않는 이유입니다.

    업데이트 : 나는 다른 두 답변 (요의와 렉스의)뿐만 아니라를 읽어 보시기 바랍니다.

  2. ==============================

    2.재귀 호출 대신 수퍼 클래스의 서브 클래스에있을; 것을 방지 할 마지막. 그런데 왜 그런 행동을 할 수 있습니다? 피보나치 시리즈는 단서를 제공하지 않습니다. 그러나이 작업을 수행합니다

    재귀 호출 대신 수퍼 클래스의 서브 클래스에있을; 것을 방지 할 마지막. 그런데 왜 그런 행동을 할 수 있습니다? 피보나치 시리즈는 단서를 제공하지 않습니다. 그러나이 작업을 수행합니다

    class Pretty {
      def recursivePrinter(a: Any): String = { a match {
        case xs: List[_] => xs.map(recursivePrinter).mkString("L[",",","]")
        case xs: Array[_] => xs.map(recursivePrinter).mkString("A[",",","]")
        case _ => a.toString
      }}
    }
    class Prettier extends Pretty {
      override def recursivePrinter(a: Any): String = { a match {
        case s: Set[_] => s.map(recursivePrinter).mkString("{",",","}")
        case _ => super.recursivePrinter(a)
      }}
    }
    
    scala> (new Prettier).recursivePrinter(Set(Set(0,1),1))
    res8: String = {{0,1},1}
    

    프리티 호출이 꼬리 재귀라면, 우리는 {세트 (0, 1), 1} 인쇄 대신에 확장 적용되지 않을 것이기 때문에 것입니다.

    재귀 이런 종류가 그럴듯 유용하며, final이 아닌 방법에 꼬리 호출이 허용 된 경우 파괴 될 수 있기 때문에, 컴파일러는 대신 실제 전화를 삽입합니다.

  3. ==============================

    3.foo는 : 사실 (N, 고해상도) 루틴을 표시하자. 일상의 나타낸다 다른 사람의 오버라이드 (고해상도, n)의 바즈 :: 사실을 보자.

    foo는 : 사실 (N, 고해상도) 루틴을 표시하자. 일상의 나타낸다 다른 사람의 오버라이드 (고해상도, n)의 바즈 :: 사실을 보자.

    컴파일러는 의미가 바즈 :: 사실은 ()이하고자하는 경우 상향 호출 할 수있는 래퍼 (?) foo는 :: 사실을 () 할 수 있음을 말하고있다. foo는 : 사실은 () 꼬리 재귀 동안 이러한 시나리오에서, 규칙, 즉 foo는 : 사실 ()가 반복되는 경우, 바즈 :: 사실 (활성화해야합니다)보다는 foo는 : 사실 ()이며, , 바즈 : 사실은 ()하지 않을 수 있습니다. 그 시점에서, 오히려 꼬리 재귀 호출, foo는 :: 사실에 루프보다 () 바즈에 반환해야합니다 :: 사실을 (), 그 자체가 긴장을 풀 수 있습니다.

  4. ==============================

    4.아무것도 잘못하지 않을 것이다. 적절한 꼬리 호출 제거와 모든 언어이 (SML, OCaml의 등 F #, 하스켈)을 할 것입니다. 스칼라하지 않는 유일한 이유는 JVM이 꼬리 재귀를 지원하지 않으며 고토와 꼬리의 위치에 자기 재귀 호출을 대체 스칼라의 일반적인 해킹이 경우에는 작업을하지 않습니다. F #이하는대로 CLR에 스칼라는이 작업을 수행 할 수 있습니다.

    아무것도 잘못하지 않을 것이다. 적절한 꼬리 호출 제거와 모든 언어이 (SML, OCaml의 등 F #, 하스켈)을 할 것입니다. 스칼라하지 않는 유일한 이유는 JVM이 꼬리 재귀를 지원하지 않으며 고토와 꼬리의 위치에 자기 재귀 호출을 대체 스칼라의 일반적인 해킹이 경우에는 작업을하지 않습니다. F #이하는대로 CLR에 스칼라는이 작업을 수행 할 수 있습니다.

  5. ==============================

    5.질문 자체가 혼란 때문에이 질문에 대한 인기가 허용 대답은 실제로 오해의 소지가있다. 영업 이익은 tailrec 및 총 소유 비용 (TCO) 사이의 구별을하지 않고, 대답은이 문제를 해결하지 않습니다.

    질문 자체가 혼란 때문에이 질문에 대한 인기가 허용 대답은 실제로 오해의 소지가있다. 영업 이익은 tailrec 및 총 소유 비용 (TCO) 사이의 구별을하지 않고, 대답은이 문제를 해결하지 않습니다.

    핵심은 tailrec에 대한 요구 사항이 TCO에 대한 요구 사항보다 더 엄격하다는 것이다.

    tailrec 주석은 TCO가 어떤 기능에 꼬리 호출에 사용할 수있는 반면에 꼬리 호출, 동일한 기능을하게되어 있어야합니다.

    꼬리 위치에서 호출이 있기 때문에 컴파일러는 사실에 총 소유 비용 (TCO)을 사용할 수 있습니다. 구체적으로는 적절 스택을 조정하여 사실에 점프로 사실에 전화를 돌 수 있었다. 사실이 버전의 호출을 함수와 동일하지 않습니다 중요하지 않습니다.

    허용 대답이 제대로되지 않은 최종 기능 tailrec 수없는 이유를 설명하기 때문에 할 수 있습니다 꼬리 호출이 같은 기능이 아닌 함수의 오버로드 된 버전 것을하지 보증. 그래서 그러나 그것은 잘못 사실이 완벽하게 안전하고 좋은 최적화 될이 방법에 총 소유 비용 (TCO)을 사용하는 것이 안전하지 않습니다 것을 의미한다.

    [존 Harrop에 의해 설명 된 바와 같이, 당신은 JVM에 총 소유 비용 (TCO)을 구현할 수 있지만, 컴파일러가 아닌 언어의 제한, 그리고 것을 참고 tailrec에 관련이]

    그리고 참조를 위해, 여기 당신이 방법의 최종하지 않고 문제를 방지 할 수있는 방법입니다 :

    class C {
      def fact(n: Int): Int = {
        @tailrec
        def loop(n: Int, result: Int): Int =
          if (n == 0) {
            result
          } else {
            loop(n - 1, n * result)
          }
    
        loop(n, 1)
      }
    }
    

    루프가 구체적인 기능보다는 방법이고 오버라이드 (override) 할 수 없기 때문에이 작동합니다. 이 버전은 또한 사실에 가짜 결과 매개 변수를 제거하는 장점이있다.

    이것은 모든 재귀 알고리즘 패턴 I을 사용하는 것입니다.

  6. from https://stackoverflow.com/questions/4785502/why-wont-the-scala-compiler-apply-tail-call-optimization-unless-a-method-is-fin by cc-by-sa and MIT license