Created
April 9, 2012 15:49
-
-
Save kohske/2344367 to your computer and use it in GitHub Desktop.
source text for the qiita
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(Rの)プロミス問題 その2。 | |
http://qiita.com/items/660eac769fa1a82fbf42 の続き。 | |
## Promiseとは | |
非常にざっくり言うと、プロミスとは「レシピと調理場を指定されたおっさん」です。 | |
プロミスを作るということは、おっさんにレシピと調理場を教えといて、いつでも必要なときにご飯を出してもらえるように準備しといてもらいます。 | |
おっさんは、「メシっ」って言われてら、調理場に走って行って、レシピを見て、料理を作ってくれます。 | |
なお、おっさんは、料理は作ってくれるんですが、それを見せてくれるだけです。 | |
おっさんは料理を一回作ったら、ずっとそれ持ってます。で、「メシっ」って言われたら、毎回それ見せてくれます。 | |
当然ですが、ここではレシピが「式」、調理場が「環境」のことです。 | |
## Promiseの中身 | |
Rのpromiseの宣言はこんな感じです。 | |
```c:src/include/Rinternals.h | |
struct promsxp_struct { | |
struct SEXPREC *value; // 値 | |
struct SEXPREC *expr; // 式 | |
struct SEXPREC *env; // 環境 | |
}; | |
``` | |
promiseは3つの要素、式と環境と値でできています。 | |
## 式と環境と値 | |
今のところpromiseの作成は`src/main/memory.c`の`mkPROMISE`で作られます。但し、CXXPプロジェクトというのが走っていて、RのバックエンドをCPPで書きなおそうというのの中にココらへんも入ってくるので、そのうち変わるかもしれません。 | |
`mkPROMISE`はこんな感じです。 | |
```c:src/main/memory.c | |
SEXP attribute_hidden mkPROMISE(SEXP expr, SEXP rho) | |
{ | |
SEXP s; // セクピー型 | |
/* snip */ | |
TYPEOF(s) = PROMSXP; // プロミス型宣言 | |
PRCODE(s) = CHK(expr); // 式 | |
PRENV(s) = CHK(rho); // 環境 | |
PRVALUE(s) = R_UnboundValue; // 値 | |
return s; | |
} | |
``` | |
プロミスが作られると、環境(調理場)と式(レシピ)を持ったプロミス型セクピー(おっさん)が作られます。例えば以下のRコード | |
```R | |
> delayedAssign("x", {print(a); b}) | |
``` | |
では、式として`{print(a); b}`、環境として`.GlobalEnv`を持ったプロミス型セクピーが作られます。 | |
ここが大事な事なんですが、上の式を実行した時にはまだ`{print(a); b}`は全然評価されていません。だから`a`も`b`も無くてもエラーになんてなりません。 | |
## promiseの値を参照する | |
プロミスはいつか実際に使われるかもしれませんし、使われないかもしれません。使われるときに初めて評価されます。これは以下の通り。関係なさそうなとこは省略してます。 | |
```c:src/main/eval.c | |
static SEXP forcePromise(SEXP e) | |
{ | |
if (PRVALUE(e) == R_UnboundValue) { // 値がunboundなら | |
SEXP val; // 値の実体を作成。皿を作る、的な。 | |
val = eval(PRCODE(e), PRENV(e)); // 式を環境で評価。料理中・・・ | |
SET_PRVALUE(e, val); // 値を設定。おっさんができた料理を持ってる。 | |
SET_PRENV(e, R_NilValue); // 環境をnilに。調理場のことは忘れる。 | |
} | |
return PRVALUE(e); // 値を返す。料理を見せる。 | |
} | |
``` | |
というわけで、もし値が初期値`R_UnboundValue`のままだったら持ってた式を持ってた環境で評価して、その結果をプロミスの値にセットします。で、最後に値を返します。 | |
Rのコードと関連付けると、 | |
```R | |
> delayedAssign("x", {print(a); b}) # 今プロミス作った: mkPROMISE | |
> x # 今評価した。でもaが見つからない。:forcePromise | |
以下にエラー print(a) : オブジェクト 'a' がありません | |
> rm(x) | |
> delayedAssign("x", {print(a); b}) # 今プロミス作った。 | |
> a <- 1 # aとbを置いといた。 | |
> b <- 2 | |
> x # 今評価した。すでにaとbがあるのでエラーにならない。 | |
[1] 1 # これはprint(a)が評価された結果。 | |
[1] 2 # これはxの値。 | |
> x # もう一回値を参照してみるけど、もう実行済みなので`print(a)`は実行されない。 | |
[1] 2 # xの値。 | |
``` | |
こうなります。 | |
上の例では、最初はおっさんは料理作りに`.GlobalEnv`に行くんですが、そのには`a`と`b`がないので、作ってくれません。 | |
なので、`a`と`b`を`.GlobalEnv`においておくと、今度はちゃんと作ってくれます。 | |
これも大事な事なんですが、`a`と`b`は、プロミス参照前であればいつ作っても問題ありません。っていうかプロミス評価時の値が使われます。 | |
おっさんはレシピ渡された時に調理場がどうだったかなんて、興味ないわけです。 | |
料理するときに調理場に必要なものがあるかどうか、それだけが大事です。 | |
## 関数の引数もプロミスなんだって?? | |
そうです。っていうかむしろこっちのほうが有名です。 | |
```R | |
> f <- function(a) {} | |
> f({print(1)}) | |
NULL | |
``` | |
`a`はプロミスです。`a`の式は`{print(1)}`ですが、`a`がどこでも使われてないので、この式は評価されません。 | |
## 関数の引数のプロミスの環境はどこ? | |
佳境です。 | |
関数の引数のプロミスの環境は、関数を呼び出した環境です(多分)。 | |
というか関数呼び出しのときに引数に渡す式を作成した環境です(多分)。 | |
```R | |
> f <- function(a) {a} # 引数(プロミス)を評価する関数 | |
> f({print(environment())}) # 環境を表示する式を引数に。 | |
<environment: R_GlobalEnv> # グローバル環境 | |
> environment(f) # 現在はグローバル環境 | |
<environment: R_GlobalEnv> | |
> | |
> g <- function() { | |
+ f({print(environment())}) # 関数内から呼び出す。 | |
+ print(environment()) # 関数呼び出しで作られる環境(fを呼び出す環境)を表示 | |
+ } | |
> | |
> g() | |
<environment: 0x1199a5390> # 同じ | |
<environment: 0x1199a5390> | |
``` | |
プロミスのたらい回しをすると、 | |
```R | |
> f <- function(a) {a} # プロミスを評価する関数 | |
> g <- function(a) f(a) # 何もしないでfを呼び出す関数 | |
> g({print(environment())}) # さて | |
<environment: R_GlobalEnv> # グローバル | |
``` | |
どこで式を作ったか、が大事です。 | |
```R | |
> f <- function(a) {a} # プロミスを評価する関数 | |
> g <- function() { # 関数を返す関数 | |
+ function() { | |
+ print(environment()) # その関数では、環境を表示して、 | |
+ f({print(environment())}) # fを呼び出す | |
+ } | |
+ } | |
> g()() # さて | |
<environment: 0x11a033580> | |
<environment: 0x11a033580> | |
``` | |
この場合、関数呼び出しは`GlobalEnv`ですが、式の作成は`g()()`の中で行われているので、その時の環境がプロミスを評価する環境になります。 | |
原理はこんなところなんですが、罠がたくさんあるのでつづく。 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment