いきます。
遅延評価
突然ですが、Rは遅延評価な言語です。
> f <- function(x) {invisible(NULL)}
> f(print(1))
関数の引数に渡されたもの(x
)は、それが使われない限り評価されません。
> f <- function(x) {force(x); invisible(NULL)}
> f(print(1))
[1] 1
使われると評価されます。
遅延評価は関数外でも使われます。
> delayedAssign("x", {print("evaluated"); 1})
> x
[1] "evaluated"
[1] 1
delayedAssign
は遅延評価用の代入です。この場合、x
が評価されない限り、x
の中身は評価されません。
> x <- {print("evaluated"); 1}
[1] "evaluated"
> x
[1] 1
普通は代入即評価です。
パッケージ内の変数・関数も遅延評価
普段は気づきませんが、library(package)
とかすると、パッケージの変数にアクセスできるようになります。ただし、この段階ではR様は変数名、関数名をリストアップするだけで、実際に変数、関数の評価はされません。lazyLoading
という仕組みです。その変数・関数にアクセスした時に初めて、内容がメモリ上に展開されます。
遅延評価による混乱
ほぼFAQですが、以下の例で質問をよく見ます。
> f <- function(a) function() a # 関数を返す関数
> r <- list()
> for (i in 1:3) r[[i]] <- f(i)
> print(r[[1]]())
[1] 3 # 現在のiの値
> print(r[[3]]())
[1] 3
f
は関数を返す関数で、その関数はr[[i]] <- f(i)
の段階では評価されていません。
print(r[[1]]())
のときに初めて、function() a
のa
が評価されます。
さて、環境の話を思い出して欲しいんですが、r[[i]]
つまりfunction() a
の環境はf()
の呼び出し時に作られた環境です。なので、r[[1]]
とr[[2]]
の環境は異なります。
> environment(r[[1]])
<environment: 0x11b01e310>
> environment(r[[2]])
<environment: 0x11b012588>
この環境にa
の中にあるんでしょうか?
> ls(environment(r[[1]]))
[1] "a"
あります。ではこのa
の値は何でしょうか?
> get("a", environment(r[[1]]))
[1] 3
3
です。
もうちょっと色々な例を上げておきます
> f <- function(a) function() a # 関数を返す関数
> r <- list()
> for (i in 1:3) {r[[i]] <- f(i); print(r[[i]]())} #評価もする
[1] 1
[1] 2
[1] 3
> print(r[[1]]())
[1] 1 # ちゃんと1になってる
> print(r[[3]]())
[1] 3
代入の直後に一回関数を評価(r[[i]]()
)しておくと、i
が3
になった後でもちゃんと1
が帰ってきます。
> f <- function(a) function() a # 関数を返す関数
> r <- list()
> for (i in 1:3) {r[[i]] <- f(i)}
> print(r[[1]]())
[1] 3
> print(r[[3]]())
[1] 3
> i <- 5 # iの値を変更
> print(r[[1]]())
[1] 3 # r[[1]]は影響を受けない
> print(r[[2]]())
[1] 5 # r[[2]]は影響を受ける
上の例の場合、i
の値が変わっている前にr[[1]]
は一度評価されているので影響を受けません。
r[[2]]
はi <- 5
の段階で見評価なので、影響を受けます。
クロージャの話
> f <- function(a) function() a
> m <- f(1)
> m
function() a
<environment: 0x1264f9008>
のf
の呼び出しで帰ってくる関数m
はクロージャと言われます。実際のところ、Rではすべての関数はクロージャですが、上の例は明示的にクロージャっぽいのでクロージャと言われます。
上の例では、クロージャの環境はf()
呼び出し時に動的に作られた環境です。
> f <- function(a) {print(environment()); function() a} #環境を表示して、クロージャを返す
> m <- f(1)
<environment: 0x1233e2208> # これと
> environment(m)
<environment: 0x1233e2208> # これ同じ
もちろん、もう一回呼び出したら違う環境が作られます。
> f <- function(a) {print(environment()); function() a}
> m <- f(1)
<environment: 0x1233e0898> # さっきと違う。
> environment(m)
<environment: 0x1233e0898>
クロージャ作成時の値を使う正しい方法。
上の例でクロージャの環境内の変数a
はクロージャ作成時には評価されていません。
なので、問題を解決するためにはクロージャ作成時にクロージャの環境内でa
を評価してあげればいい、ということになります。
> f <- function(a) {
+ force(a) # aを強制的に評価
+ function() a # クロージャを返す
+ }
> r <- list()
> for (i in 1:3) {r[[i]] <- f(i)}
> print(r[[1]]())
[1] 1
> print(r[[3]]())
[1] 3
という具合です。
force
というのは引数を強制的に評価する関数なんですが、その実装は
> force
function (x)
x
<bytecode: 0x121195f58>
<environment: namespace:base>
です。別にforce
じゃなくても、一旦クロージャの環境で値を評価すれば、何を使っても大丈夫です。クロージャ作成の後でも大丈夫です。
> f <- function(a) {
+ z <- function() a # クロージャを作る
+ force(a) # aを強制的に評価
+ z # クロージャを返す
+ }
> r <- list()
> for (i in 1:3) {r[[i]] <- f(i)}
> print(r[[1]]())
[1] 1
> print(r[[3]]())
[1] 3
ただまあ、最初にforce
しておく、というのが正式な方法だと思います。
何が起こってるのか?
次回はpromiseの説明をします。
つづく。