AtsushiさんのQiita記事を読んでちょっと興味が湧いたので調べたことをメモ。
記事にあった問題をわかりやすくすると、こんな感じ。
print.function <- function(x, ...) { base::print.default(deparse(substitute(x))) } # printを直接呼ぶとxはちゃんと関数名にsubstitute()される print(max) #> [1] "max" # REPL越しにprintを呼ぶとxになる max #> [1] "x"
Rのコードを追ったところ、呼び出し側の環境でx
という名前で引数を渡しているからみたいです。REPLの実装はこのあたりです。
特に注目はこのへん。eval()
でコードを評価して、R_visible
がtrue
だったらPrintValueEnv()
という関数を呼んで結果を表示するっぽいです。
PROTECT(value = eval(thisExpr, rho));
SET_SYMVALUE(R_LastvalueSymbol, value);
wasDisplayed = R_Visible;
if (R_Visible)
PrintValueEnv(value, rho);
その実装はこのあたり。
なんか書いてあります。
/* Bind value to a variable in a local environment, similar to a local({ x <- <value>; print(x) }) call. This avoids problems in previous approaches with value duplication and evaluating the value, which might be a call object. */ PROTECT(call = lang2(prinfun, xsym)); PROTECT(env = NewEnvironment(R_NilValue, R_NilValue, env)); defineVar(xsym, s, env); eval(call, env); defineVar(xsym, R_NilValue, env); /* to eliminate reference to s */
ということで、x
が返ってくるのは、print()
の引数がx
だからではなく、RのREPLが新しい環境をつくってそこで値をx
という変数に入れてからprint()
に渡すから、ということのようです。
これはお手上げ感ありますが、/* to eliminate reference to s */
とあるので、その実体を参照している変数の名前を取る方法があればあるいは...?
難しそうなのでこの辺でギブアップ。
余談
ちなみに、メソッドディスパッチは元の引数がそのまま渡されるっぽいので今回は違いましたが、以下のように、関数呼び出しがひとつ挟まるので元の引数が取れない、みたいなことがあります。
fun_outside <- function(x) { fun_inside(x) } fun_inside <- function(x) { base::print.default(deparse(substitute(x))) } fun_outside(max) #> [1] "x"
こういう問題であれば、呼び出し側の環境でsubstitute()
するという手があります。
substitute()
にはenv
という引数が用意されているので、ここにparent.frame()
で呼び出し環境を指定します。
fun_inside <- function(x) { base::print.default(deparse(substitute(x, parent.frame()))) } fun_outside(max) #> [1] "max"
再帰的に環境を駆け上がっていく方法もあるのかな…?