あるオブジェクトをRのコンソール画面で定義したとする。
例えば、
hoge <- 1
というものがあったとする。
やりたいことは、このオブジェクトの値を関数の中から変更したい。
難しいのは、関数内で定義されたオブジェクトは、同じ名前だったとしても、グローバル環境で定義されたものとはスコープが違うので、関数内で変更を加えても、グローバル環境には変更が及ばない。
library("pryr")
hoge <- 1
where("hoge") # <environment: R_GlobalEnv>にある
changeValue <- function(hoge){
print(where("hoge")) # <environment: 0x7fd747dd4a40>にある
}
changeValue(hoge)
ただ、これだけなら、単純に永続代入(<<-)すれば、簡単に変更できる。
hoge <- 1
changeValue <- function(){
hoge <<- 2 # 変更
}
changeValue()
hoge # 2
しかし、ここではさらに、この変更関数を、後々以下のようにあるクラスに対するメソッドとして定義していきたい。
setGeneric("changeValue", function(obj){standardGeneric("changeValue")})
setMethod("changeValue", "hoge-class", function(hoge){
# hogeに対する何らかの変更処理
})
なので、changeValueの引数は、hoge-classからインスタンス化されたオブジェクトであってほしい。
このしばりのせいで、地味にはまったので、色々試行錯誤したものをメモ。
引数を文字列にしてみた
hoge <- 1
changeValue <- function(hoge2){
hoge <- eval(parse(text=hoge2))
hoge <- 2 # 変更
hoge <<- hoge
}
changeValue("hoge")
hoge # 2
changeValueの引数を文字列として、eval & parse & textで評価して、変更を加え、永続代入。
これだと、引数が文字列なので、やりたいこととずれる。
グローバル環境に一回検索をかけて、上書き
hoge <- 1
changeValue <- function(hoge2){
userobjects <- ls(env=.GlobalEnv)
target <- which(sapply(userobjects, function(x){
identical(eval(parse(text=x)), hoge2)
}))
hoge2 <- 2 # 変更
assign(names(target), hoge2, envir=.GlobalEnv)
}
changeValue(hoge)
hoge # 2
これは一番やりたいことと近かったのだが、identicalで同じオブジェクトをグローバル環境にあるか探索することに頼っているので、同じものが複数定義されていた時に、どっちが変更されるかわからない。
実際、hogeと同じ内容のオブジェクトが2つ以上あると、最初の1個のほうを使いましたという警告が出てしまう。
deparse & substitute
hoge <- 1
changeValue <- function(hoge2){
target <- deparse(substitute(hoge2))
hoge2 <- 2 # 変更
assign(target, hoge2, envir=.GlobalEnv)
}
changeValue(hoge)
hoge # 2
そもそも、hogeという名前を、関数の中で取得することが難しいから、上のように色々やっていたのだが、
deparse & substituteを使えば、この名前が取得できるらしい(https://stackoverflow.com/questions/10520772/in-r-how-to-get-an-objects-name-after-it-is-sent-to-a-function )。
結局、これを知らなかったからはまっただけだった。
Julia言語ではmutableな関数として!つけるだけのことなのだが...