Rと言えば遅延実行
最近はPythonのデコレータ、Juliaのマクロのように遅延実行をサポートするプログラミング言語も増えてきましたが、Rの引数は常に遅延実行の世界です。意識しておくと便利なこともあるので、特徴を把握しておきましょう。
1 引数は使われるまで評価されない
例を見るとすぐに理解できると思うのですが、関数を呼ぶときにつけた引数は、関数内で参照されるまで評価されない仕組みが遅延実行です。
<- 0
x <- function(arg){
fn <<- 1 # super assignmentで親スコープのxを更新
x
arg
}fn(x^2)
[1] 1
関数を呼んだ時点のx
は0
ですが、関数内で引数を使う前に1
に更新されています。結果は1
なので、関数を使うときにx^2
が評価されたことが分かります。なお、遅延実行される式を予約(Promise)オブジェクトと呼びます。
2 ローカルスコープの変数は参照されない
引数の変数は呼び出し元のスコープのxになるので注意してください。以下のようにローカルスコープの変数は参照されません。
<- 0
x <- function(arg){
fn <- 1 # ローカルスコープにxを定義
x
arg
}fn(x^2)
[1] 0
3 引数は数式でなくてもよい
{
と}
で囲まれた式も引数として渡すことができます。
<- function(arg){
fn print("2")
arg
}fn({ print("1") })
[1] "2"
[1] "1"
関数を呼んだ時点で引数を評価していれば、以下のような結果になるわけですが、逆なので遅延実行していることが分かります。
[1] "1"
[1] "2"
3.1 小技
system.time({ })
は遅延実行を活かして、実行前の時間の測ってから、引数の{ }
を実行することで処理時間を測っています。
真似をして、
<- function(filename, arg){
saveplot png(filename, type="cairo")
argdev.off()
}
saveplot("example.png", { curve(x^2, -1, 1) })
としたら、プロットの保存と、プロット自体を分離することができます。例えばR Markdownでの利用のように、ファイル保存などを行なわないコードで描画するプロットを、別の目的で保存したくなったときなどに便利です。画面にプロットしてから保存もできますが。
4 引数でなくても遅延評価にできる
こちらは明示的に書く必要がありますが、遅延評価で変数に値を割り当てることもできます。
<- 1
x delayedAssign("y", x^2)
<- 3
x print(y)
[1] 9
式にして後で評価しても良い気がしますが、eval
しなくても値が入るので便利かも知れません。
組み合わせても遅延評価されます。delayedAssign
でつくった予約オブジェクトを関数の引数にとっても、関数内で参照されるまでdelayedAssign
で指定した式が評価されないです。
<- 1
x delayedAssign("y", x^2)
<- 2
x <- function(arg){
fn <<- 3
x
arg
}fn(y)
[1] 9
5 まとめ
意識しないと即時評価も遅延評価も差異はないのですが、遅延評価を上手く使うとコードの再利用性が高まります。Rらしいコードにもなりますし、ややこしい記述でもないので、意識して使っていきたいですね。