Rの環境&パッケージ作成TipsおよびRGtk2ちょっとだけ

タイトルの通りです。

パッケージ内に子環境を作ってオブジェクトを格納する

自作パッケージの関数で,パッケージの内部でしか使わない関数を大局的環境に読み込まないようにしたかった。twitterで聞いてみたところ,environmentという助言をid:syou6162にいただきました。

つまり,パッケージ内に子環境を作ってその中にオブジェクトを格納しておけば,子環境自体は大局的環境に読み込まれるものの,子環境の中のオブジェクトは大局的環境に読み込まれないわけです。

[パッケージ環境(大局的環境に読み込まれるオブジェクト[子環境(大局的環境に読み込まれないオブジェクト)])]

こんな入れ子状になります。これは簡単にできます。

env.for.package <- new.env()
assign("f", function(x, y){x*y}, envir=env.for.package)

こんな風なのをパッケージに含めるだけです。library(...)で読み込んだとき,env.for.packageにはアクセスできますが,f()にはアクセスできません。アクセスする方法は何通りかあります。以降,とくにパッケージ作成とは関係なく環境の話です。

f <- get("f", envir=env.for.package)
f(2, 3)

env.for.package$f(2, 3)

with(env.for.package,f(2, 3))

attach(env.for.package)
f(2, 3)
detach(env.for.package)

eapply(env.for.package, eval)[[1]](2, 3)

getはassignと対になる関数で,環境からオブジェクトを取得します。assignは環境にオブジェクトを登録します。二つ目の方法は,環境は実はlist風の操作ができるので,こんな風にして環境内のオブジェクトにアクセスできます。これが一番便利です。その次のwithとattach・detachはdata.frameをよくいじる人は使ってるかもしれません。withは一時的に環境やdata.frame内のオブジェクトにアクセスする関数です。attachはサーチパスに環境やdata.frameを加えます。eapplyは普段日の目を見ることがないレアな関数を使ってみただけです…。

一時環境の作成というのはいろいろ応用ができると思います。不用意にいじってはいけないオブジェクトを格納しておいたり,種類別に変数を分類したり。あ,でもそれはlistでもいいですね…一時環境の活用方法,いいのがあったら教えて下さい。

もっと環境に詳しくなりたい場合は,Rプログラミングマニュアル (新・数理工学ライブラリ 情報工学)で。

さて,当初の目的ですが,実はNAMESPACEで必要ない関数をexportしないだけでよかったorz exportに書かなかったオブジェクトはパッケージ内部でも使えないと思い込んでたんですが,勘違いでした。

以下はRGtk2に興味がない人は飛ばして下さい。

    • -

なぜそんな勘違いをしていたかというと,RGtk2を使ったパッケージを作ってまして,RGtk2のドキュメントに以下のように書いてあったのが原因です。

gSignalConnect(btn, "clicked", quote(cat("Saying hello from the button\n")))

ようはbtnという名前のボタンが押されたときにcat(...)を実行するようにする設定なのですが,quote()でくくってあるのがポイントです。この場合btnに結びつけられるのはcallであって関数等とはこの時点では関わりがありません。このcallはbtnが押されたときにevalされるわけですが,この際に大局的環境でevalされるみたいです。

ここが勘違いの原因で,僕はここでパッケージ内かつexportしていない関数をバインディングしていたために,evalされたときにそんな関数ないって怒られたわけです。それを,exportしていないのが原因でexportしなければパッケージ内部でも使えないと勘違いしたのでした。

これを回避するためには,以下のように書き換えます。

gSignalConnect(btn, "clicked", cat, "Saying hello from the button\n")

こうすると,オブジェクトそのものがバインドされるので安心です。このあたりも環境をちゃんと把握してないからはまったわけで,なかなか奥が深いです。

その他のTips

library()で読み込んでいないパッケージの関数にアクセスする

pkgname::function()のように2重コロンを使うとアクセスできます。pkgname:::function()のように3重コロンにすると,exportされていない内部関数にもアクセスできます。ソースを追っているときなどに便利です。

注意点ですが,この方法で呼び出した関数が他のパッケージの関数に依存している場合は当然走りません。また,総称的関数を呼び出した場合も,実際呼ばれるべき個別の関数が大局的環境にないので走りません。

雑多なファイルをパッケージに含める

ちょっとはパッケージの話を。パッケージのソースのディレクトリにinstというディレクトリを作っておくと,instの中身がパッケージに含まれます。instのままではなく,instの中身がパッケージの一番上の階層に展開されます。

例えば,画像や設定ファイルなどを入れておく,という用途が考えられます。アクセスしたいときは,paste(.Library, "pkgname", sep="/")とすればパッケージのディレクトリのパスが得られます。.Libraryにはライブラリのパスが格納されています。

ということでまとまりのない内容ですみません。