このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

R言語メタプログラミングの基礎:補足

R言語メタプログラミングの基礎」に、tobさんからコメントをいただいて、それについて書こうと思っていたのですが、だいぶ間が開いてしまいました。ここに補足として記します。

内容:

  1. 空な名前
  2. 関数オブジェクトのコンストラク
  3. テキストからコールオブジェクトやエクスプレッションオブジェクトを作る
  4. オブジェクトを表現するテキストを得る
  5. テキストを加工してオブジェクトを作る

空な名前

ユーザーレベルで空な名前はどうやっても作れないようです。

as.name("") も quote() もエラーとなります。しかし、tobさん情報によると、quote(expr =) は許されるとのこと。実際、quote(expr =) で空な名前(シンボル)が返ります。名前付き引数に空を指定する(何も指定しない)ことが許されとは思わなかった、知らないと思い付かないですよね。

このことを利用すれば、noDefault()関数は次のように書けます。

noDefault <- function() quote(expr =)

関数オブジェクトのコンストラク

関数オブジェクトのコンストラクタは`function`関数だと思うのですが、呼び出し方がわかりません。

次のようにして関数を作ることは出来ませんでした。

add <- `function`(pairlist(x = noDefault(), y = noDefault()), quote(x + y))

しかし、tobさん情報によると、callコンストラクタとeval関数をかますとうまくいきます。

add <- eval(call("function", pairlist(x = noDefault(), y = noDefault()), quote(x + y)))

普通は、eval(call(NAME, ARG1, ARG2)) は、NAME(ARG1, ARG2) と等価です。例えば:


> eval(call("list", c(1, 2), c(3, 4)))
[[1]]
[1] 1 2

[[2]]
[1] 3 4

> list(c(1, 2), c(3, 4))
[[1]]
[1] 1 2

[[2]]
[1] 3 4

>

あえて、eval(call("list", c(1, 2), c(3, 4))) とする人はいないでしょう。でも、`function`のときは等価じゃなかったのです。持って回ったやり方だとうまくいく、と。何でだろう、コレ。

テキストからコールオブジェクトやエクスプレッションオブジェクトを作る

Rの式を書いたテキストからエクスプレッションオブジェクトを作るには、parse()関数を使います。第1引数はファイル名なので、parse("foo.R") のように使います。文字列をパーズしたいなら、parse(text="x + 2") とかします。


> parse(text="x + 2")
expression(x + 2)
> parse(text="x + 2")[[1]]
x + 2
> eval(parse(text="x + 2"))
以下にエラー eval(expr, envir, enclos) : オブジェクト 'x' がありません
> x <- 5
> eval(parse(text="x + 2"))
[1] 7
>

parse()はエクスプレッションオブジェクトを返します。エクスプレッションオブジェクトはコールオブジェクトのリストなので、その成分(リスト要素)を取ればコールオブジェクトを得られます。複数の式の並びは長さ2以上のリストになります。


> parse(text="x <-3; x + 2")
expression(x <-3, x + 2)
> parse(text="x <-3; x + 2")[[1]]
x <- 3
> parse(text="x <-3; x + 2")[[2]]
x + 2
> eval(parse(text="x <-3; x + 2"))
[1] 5
> x
[1] 3
>

トップレベルで実行されたeval()はデフォルトで大域環境(.GlobalEnv)を使うので、eval()の実行で環境が書き換わることがあります。この挙動はマズイことがあるので、そのときはeval()のenvir引数で評価環境を指定しましょう。

オブジェクトを表現するテキストを得る

deparse()は、任意のオブジェクトの文字列表現を返します。parse()の完全な逆関数になっているわけではないので注意しましょう。


> "hello"
[1] "hello"
> deparse("hello")
[1] "\"hello\""
> parse(text=deparse("hello"))
expression("hello")
> deparse(parse(text=deparse("hello")))
[1] "structure(expression(\"hello\"), srcfile = , wholeSrcref = structure(c(1L, "
[2] "0L, 2L, 0L, 0L, 0L, 1L, 2L), srcfile = , class = \"srcref\"))"
>

eval(parse(text=deparse(OBJECT))) とすると再びOBJECTになります。


> eval(parse(text=deparse(10)))
[1] 10
> eval(parse(text=deparse("hello")))
[1] "hello"
> eval(parse(text=deparse(c(1, 2, 3))))
[1] 1 2 3
> eval(parse(text=deparse(function(x) x + 1)))
function (x)
x + 1
>

テキストを加工してオブジェクトを作る

テキストを切り貼りしてからparse()を使ってオブジェクトを作ることはおススメできませんが、使わざるを得ないときもあります。

curve()関数は、引数に書いた式をそのままグラフにしてくれる便利関数です。が、コールオブジェクトを引数に渡せないので、ライブラリ的な使用が困難です。


> curve(x^2 + x, xlim=c(-2, 2)) # グラフを描く
> ex <- quote(x^2 + x)
> ex
x^2 + x
> curve(ex, xlim=c(-2, 2))
以下にエラー eval(expr, envir, enclos) :
関数 "ex" を見つけることができませんでした
>

curve()関数を呼び出すコマンドラインと同じテキストを作って、parseしてevalという手順で実行できます。


> ex <- quote(x^2 + x)
> ex
x^2 + x
> deparse(ex)
[1] "x^2 + x"
> curveCallText <- paste("curve(", deparse(ex), ", xlim=c(-2, 2))")
> curveCallText
[1] "curve( x^2 + x , xlim=c(-2, 2))"
> curveCall <- parse(text=curveCallText)
> curveCall
expression(curve( x^2 + x , xlim=c(-2, 2)))
> eval(curveCall) # グラフを描く
>

対話的な利用で便利なように「引数として式をそのまま書ける」ようにすると、そのような関数を再利用することが難しくなります。対話的な関数と同じ機能で、コールオブジェクトやエクスプレッションオブジェクトを受け入れる関数も準備しておくと良いですね。