末尾最適化メモ
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;
}
としている。