関数名を直接書いた再帰呼び出しは、関数を別名に代入し直すと壊れる
再帰関数の中で自分自身を関数名で直接呼び出していると、その関数を別の変数名に代入し直して元の名前を消した途端、再帰呼び出しの箇所がobject not foundエラーになります。名前に依存せず自分自身を参照したい場合はRecall()を使う必要があります。
エラーメッセージの読み方
Error in fact(n - 1) : could not find function "fact"
Error- エラー種別 — 実行中に発生しました
fact(n - 1)- 発生箇所 — エラーの原因になった呼び出し
could not find function "fact"- 詳細メッセージ — 何が問題だったか
このエラーが出る典型パターン
パターン1
1 fact <- function(n) { 2 if (n <= 1) return(1) 3 n * fact(n - 1) ^ 4 } 5 math_fact <- fact 6 rm(fact) 7 cat(math_fact(5), "\n")
再帰呼び出しがfactという名前を直接参照していると、rm(fact)で元の名前を消した瞬間に参照先を失います。Recall()なら名前に依存せず自分自身を呼び出せます。
直し方: fact(n - 1) を Recall(n - 1) にします。
パターン2
1 fib <- function(n) { 2 if (n <= 1) return(n) 3 fib(n - 1) + Recall(n - 2) ^ 4 } 5 math_fib <- fib 6 rm(fib) 7 cat(math_fib(6), "\n")
関数名で自分自身を呼ぶ書き方は、その名前を変数から消してしまうと動かなくなります。
直し方: fib(n - 1) を Recall(n - 1) にします。
パターン3
1 count_down <- function(n) { 2 if (n <= 0) return(0) 3 count_down(n - 1) ^ 4 } 5 run_count_down <- count_down 6 rm(count_down) 7 cat(run_count_down(3), "\n")
関数の中身は代入し直しても変わりませんが、中に書かれた名前による再帰呼び出しは実行時にその名前を探しに行くため、元の名前が無いと失敗します。
直し方: count_down(n - 1) を Recall(n - 1) にします。
パターン4
1 power2 <- function(n) { 2 if (n <= 0) return(1) 3 2 * power2(n - 1) ^ 4 } 5 calc_power2 <- power2 6 rm(power2) 7 cat(calc_power2(4), "\n")
名前に依存せず自分自身を再帰的に呼び出したい場合は、関数名の直接記述ではなくRecall()を使う必要があります。
直し方: power2(n - 1) を Recall(n - 1) にします。
パターン5
1 sum_to <- function(n) { 2 if (n <= 0) return(0) 3 n + sum_to(n - 1) ^ 4 } 5 total_sum_to <- sum_to 6 rm(sum_to) 7 cat(total_sum_to(5), "\n")
元の関数名を消してしまうと、その名前で再帰呼び出しをしていた箇所はobject not foundならぬcould not find functionエラーになります。
直し方: sum_to(n - 1) を Recall(n - 1) にします。
よくある誤解
「関数の中身は代入し直しても変わらないから、再帰呼び出しも問題なく動き続けるはず」という思い込みは誤りです。関数定義の中に書かれた再帰呼び出しは、実行時にその名前を毎回スコープの中から探しに行くため、元の名前を消してしまうと参照先を失います。
実務での勘所
Recall()が名前に依存しない理由は、名前を探すという工程自体を省略しているためです。Recall()は内部的にsys.function()という仕組みを使い、「今まさに実行中のこの関数自体」を呼び出しスタックから直接参照します。名前を頼りにスコープの中を探しに行くわけではないため、その関数がどんな名前の変数に代入されていようと、常に正しく自分自身を再帰的に呼び出せます。名前による参照と、実行中の関数オブジェクトそのものへの直接参照という、まったく別の仕組みだと理解すると納得できます。