Skip to content

Instantly share code, notes, and snippets.

@kohske
Created April 9, 2012 15:49
Show Gist options
  • Save kohske/2344367 to your computer and use it in GitHub Desktop.
Save kohske/2344367 to your computer and use it in GitHub Desktop.
source text for the qiita
(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