Code Fix

上級

関数名を直接書いた再帰呼び出しは、関数を別名に代入し直すと壊れる

再帰関数の中で自分自身を関数名で直接呼び出していると、その関数を別の変数名に代入し直して元の名前を消した途端、再帰呼び出しの箇所が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")
Error in fact(n - 1) : could not find function "fact"

再帰呼び出しが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")
Error in fib(n - 1) : could not find function "fib"

関数名で自分自身を呼ぶ書き方は、その名前を変数から消してしまうと動かなくなります。

直し方: 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")
Error in count_down(n - 1) : could not find function "count_down"

関数の中身は代入し直しても変わりませんが、中に書かれた名前による再帰呼び出しは実行時にその名前を探しに行くため、元の名前が無いと失敗します。

直し方: 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")
Error in power2(n - 1) : could not find function "power2"

名前に依存せず自分自身を再帰的に呼び出したい場合は、関数名の直接記述ではなく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")
Error in sum_to(n - 1) : could not find function "sum_to"

元の関数名を消してしまうと、その名前で再帰呼び出しをしていた箇所はobject not foundならぬcould not find functionエラーになります。

直し方: sum_to(n - 1) を Recall(n - 1) にします。

この問題を解いてみる →

よくある誤解

「関数の中身は代入し直しても変わらないから、再帰呼び出しも問題なく動き続けるはず」という思い込みは誤りです。関数定義の中に書かれた再帰呼び出しは、実行時にその名前を毎回スコープの中から探しに行くため、元の名前を消してしまうと参照先を失います。

実務での勘所

Recall()が名前に依存しない理由は、名前を探すという工程自体を省略しているためです。Recall()は内部的にsys.function()という仕組みを使い、「今まさに実行中のこの関数自体」を呼び出しスタックから直接参照します。名前を頼りにスコープの中を探しに行くわけではないため、その関数がどんな名前の変数に代入されていようと、常に正しく自分自身を再帰的に呼び出せます。名前による参照と、実行中の関数オブジェクトそのものへの直接参照という、まったく別の仕組みだと理解すると納得できます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)