LoginSignup
3
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2012-04-09

http://qiita.com/items/660eac769fa1a82fbf42 の続き。

Promiseとは

非常にざっくり言うと、プロミスとは「レシピと調理場を指定されたおっさん」です。
プロミスを作るということは、おっさんにレシピと調理場を教えといて、いつでも必要なときにご飯を出してもらえるように準備しといてもらいます。
おっさんは、「メシっ」って言われてら、調理場に走って行って、レシピを見て、料理を作ってくれます。
なお、おっさんは、料理は作ってくれるんですが、それを見せてくれるだけです。
おっさんは料理を一回作ったら、ずっとそれ持ってます。で、「メシっ」って言われたら、毎回それ見せてくれます。

当然ですが、ここではレシピが「式」、調理場が「環境」のことです。

Promiseの中身

Rのpromiseの宣言はこんな感じです。

src/include/Rinternals.h
struct promsxp_struct {
    struct SEXPREC *value; // 値
    struct SEXPREC *expr; // 式
    struct SEXPREC *env; // 環境
};

promiseは3つの要素、式と環境と値でできています。

式と環境と値

今のところpromiseの作成はsrc/main/memory.cmkPROMISEで作られます。但し、CXXPプロジェクトというのが走っていて、RのバックエンドをCPPで書きなおそうというのの中にココらへんも入ってくるので、そのうち変わるかもしれません。

mkPROMISEはこんな感じです。

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コード

> delayedAssign("x", {print(a); b})

では、式として{print(a); b}、環境として.GlobalEnvを持ったプロミス型セクピーが作られます。
ここが大事な事なんですが、上の式を実行した時にはまだ{print(a); b}は全然評価されていません。だからabも無くてもエラーになんてなりません。

promiseの値を参照する

プロミスはいつか実際に使われるかもしれませんし、使われないかもしれません。使われるときに初めて評価されます。これは以下の通り。関係なさそうなとこは省略してます。

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のコードと関連付けると、

> 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に行くんですが、そのにはabがないので、作ってくれません。
なので、ab.GlobalEnvにおいておくと、今度はちゃんと作ってくれます。

これも大事な事なんですが、abは、プロミス参照前であればいつ作っても問題ありません。っていうかプロミス評価時の値が使われます。

おっさんはレシピ渡された時に調理場がどうだったかなんて、興味ないわけです。
料理するときに調理場に必要なものがあるかどうか、それだけが大事です。

関数の引数もプロミスなんだって??

そうです。っていうかむしろこっちのほうが有名です。

> f <- function(a) {}
> f({print(1)})
NULL

aはプロミスです。aの式は{print(1)}ですが、aがどこでも使われてないので、この式は評価されません。

関数の引数のプロミスの環境はどこ?

佳境です。

関数の引数のプロミスの環境は、関数を呼び出した環境です(多分)。
というか関数呼び出しのときに引数に渡す式を作成した環境です(多分)。

> 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>

プロミスのたらい回しをすると、

> f <- function(a) {a} # プロミスを評価する関数
> g <- function(a) f(a) # 何もしないでfを呼び出す関数
> g({print(environment())}) # さて
<environment: R_GlobalEnv> # グローバル

どこで式を作ったか、が大事です。

> f <- function(a) {a} # プロミスを評価する関数
> g <- function() { # 関数を返す関数
+   function() {
+       print(environment()) # その関数では、環境を表示して、
+       f({print(environment())}) # fを呼び出す
+       }
+ }
> g()() # さて
<environment: 0x11a033580>
<environment: 0x11a033580>

この場合、関数呼び出しはGlobalEnvですが、式の作成はg()()の中で行われているので、その時の環境がプロミスを評価する環境になります。

原理はこんなところなんですが、罠がたくさんあるのでつづく。

3
3
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
3
3