KisaragiEffective.github.io

Yeet.

View on GitHub

末尾最適化メモ

tailrec fun a(i: Int) {
  if (cond) return base
  return calc(i, a(newParam(i)))
}

以上のような一般化された1引数の末尾再帰関数を考える。base = 0, calc = $1 + $2, newParam(i) = i - 1, cond = i > 0 と読み替えると (0..i).sum() になる。 ここで、末尾再帰はループに展開できるのでループに展開する。

fun a(i: Int) {
  var iCopy = i // parameter i is val
  var res = base
  while (!cond) {
    res = calc(res, iCopy)
    iCopy = newParam(iCopy)
  }
  return res
}

base = 0, calc = $1 + $2, newParam(i) = i - 1, cond = i > 0 で考えてみる Calling a(6):

res iCopy iCopy >= 0
- 6 -
0 6 T
6 5 T
11 4 T
15 3 T
18 2 T
20 1 T
21 0 F

よし。ちゃんと21で合っている。 任意個の引数の関数をtailrecする場合でも

res = calc(res, p1, p2, p3, /* ... */, pN)
p1 = newParam1(p1)
p2 = newParam2(p2)
p3 = newParam3(p3)
// ...
pN = newParamN(pN)

として同様に適用できる。

備考: kotlincはおそらく分岐予測のペナルティを減らすために

while(true) {
  if (cond) return acc;
}

としている。