나는 현재 스택이 오버플로되지 않도록 꼬리 호출 최적화를 지원하는 스칼라에서 ackermann 함수의 변형을 구현하는 문제를 해결하고 있습니다.
문제는 테일 콜을 최적화하는 방법을 찾을 수 없다는 것입니다. continuation-pass-style (CPS)이 도움이 될 것이라고 들었지만 CPS 스타일로 성공적으로 다시 구현했지만 여전히 길을 잃었습니다.
ackermann 함수의 변형은 다음과 같습니다.
ppa(p, a, b) = p(a, b) (if a <= 0 or b <= 0)
ppa(p, a, b) = p(a, ppa(p, a-1, b)) (if p(a, b) is even and a, b > 0)
ppa(p, a, b) = p(ppa(p, a, b-1), b) (if p(a, b) is odd and a, b > 0)
최적화가없는 코드는 다음과 같습니다.
def ppa(p: (Int, Int) => Int, a: Int, b: Int): Int = {
def ppa_cont(a: Int, b: Int, ret: (Int, Int) => Int): Int = {
if (a <= 0 || b <= 0) ret(a, b)
else (a, b) match {
case (_, _) if (p(a, b) % 2 == 0) => ret(a, ppa_cont(a-1, b, (x, y) => ret(x, y)))
case (_, _) => ret(ppa_cont(a, b-1, (x, y) => ret(x, y)), b)
}
}
ppa_cont(a, b, p)
}
또 다른 시험은 다음과 같습니다.
def ppa(p: (Int, Int) => Int, a: Int, b: Int): Int = {
def ppa_cont(a: Int, b: Int, cont: (Int, Int) => Int): (Int, Int) => Int = {
if (a <= 0 || b <= 0) cont
else if (p(a, b) % 2 == 0) (a, b) => cont(a, ppa_cont(a-1, b, cont)(a-1, b))
else (a, b) => cont(ppa_cont(a, b-1, cont)(a, b-1), b)
}
ppa_cont(a, b, p)(a, b)
}
나는 다음과 같이 꼬리 호출을 최적화하려고했습니다.
def ppa(p: (Int, Int) => Int, a: Int, b: Int): Int = {
@annotation.tailrec
def ppa_cont(a: Int, b: Int, ret: (Int, Int) => TailRec[Int]): TailRec[Int] = {
if (a <= 0 || b <= 0) tailcall(ret(a, b))
else (a, b) match {
case (_, _) if (p(a, b) % 2 == 0) => {
tailcall(ret(a, ppa_cont(a-1, b, (x, y) => ret(x-1, y))))
}
case (_, _) => {
tailcall(ret(ppa_cont(a, b-1, (x, y) => ret(x, y-1)), b))
}
}
}
val lifted: (Int, Int) => TailRec[Int] = (x, y) => done(p(x, y))
ppa_cont(a, b, lifted).result
}
그러나 이것은 유형 불일치로 인해 컴파일되지 않습니다.
무엇이 문제일까요? 내가 잘못된 방향으로 가고 있습니까? 약간의 힌트와 도움의 손길을 주시면 감사하겠습니다. 고마워 :)
추신 : 힌트를 얻었습니다 : 왜 스칼라가 꼬리 호출 최적화를하지 않습니까?
시도 cats.free.Trampoline
나 scala.util.control.TailCalls.TailRec
. @tailrec
스택에 안전 하지는 않습니다 .
import scala.util.control.TailCalls._
def ppa(p: (Int, Int) => Int, a: Int, b: Int): Int = {
def hlp(a: Int, b: Int): TailRec[Int] = {
if (a <= 0 || b <= 0) done(p(a, b))
else if (p(a, b) % 2 == 0) tailcall(hlp(a - 1, b)).map(p(a, _))
else tailcall(hlp(a, b - 1)).map(p(_, b))
}
hlp(a, b).result
}
http://eed3si9n.com/herding-cats/stackless-scala-with-free-monads.html
http://eed3si9n.com/herding-cats/tail-recursive-monads.html
실제로 귀하의 기능은 Ackermann처럼 보이지 않습니다. 실제 Ackermann은 두 번의 재귀 호출을합니다.
f(m, n) = f(m - 1, f(m, n - 1))
함수는 단일 재귀 호출을 만듭니다. 함수의 반복 버전을 작성하는 것은 어렵지 않습니다 (컴파일러가 자동으로 반복 버전으로 변환 할 수 있기 때문에 보통 꼬리 재귀가 사용됩니다). , (노란색 영역)에 ppa(i, j)
대해 이미 계산했다고 가정 합니다. 그런 다음 두 개의 주황색 세그먼트 (이 순서)와 (이 순서)를 계산합니다. 그런 다음 적혈구를 계산 합니다.0 <= i <= a - 1
0 <= j <= b - 1
(a, 0), (a, 1), ..., (a, b - 1)
(0, b), (1, b), ..., (a - 1, b)
(a, b)
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다