9
12

More than 5 years have passed since last update.

(Rの)プロミス問題 その1。

Last updated at Posted at 2012-04-07

いきます。

遅延評価

突然ですが、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() aaが評価されます。

さて、環境の話を思い出して欲しいんですが、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の説明をします。

つづく。

9
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
12