何となくもやもやしてたので。
階乗を例に:
末尾再帰じゃないやつ
const fact = x =>
(x==0)? 1
: x * fact(x - 1);
末尾再帰したやつ
const fact = (x, acc = 1) =>
(x==0)? acc
: fact(x - 1, x * acc);
ああわかった。
末尾再帰じゃないやつは次回の引数で再帰的に自分を呼んでいる。
末尾再帰は次回の引数と今回のとりあえずの結果を次回に送っている。
3の階乗で流れを追ってみます。
//末尾再帰じゃない版
fact(3)
3*fact(2)
3*(2*fact(1))
3*(2*(1*fact(0)))
3*(2*(1*1))
3*(2*1)
3*2
6
//末尾再帰版
fact(3) // fact(3, 1)と同じ
fact(2, 3*1)
fact(1, 2*3)
fact(0, 1*6)
6
厳密なものではないですが、末尾再帰じゃないほうは手順が多くなるし、数値が出揃ってから計算するのでメモリーが沢山必要ってのがわかる。