sub work_hard {
# some costly operation
}
use MT::Promise qw(delay lazy force);
# slickest
$meaning_of_life = lazy { work_hard(); return 42 };
# kinda slick
$meaning_of_life = delay(sub { work_hard(); return 42 });
# clunky ...
$meaning_of_life = new MT::promise(sub { work_hard(); return 42; });
print force($meaning_of_life);
# prints:
# 42
プロミスとは、値のようなものですが、その値自体はまだ計算されていません。任意の時点でプロミスが値を取得することを、「強制(force)」することができます。最初にforceされたときに値が計算され、その値が保存されます。2回目以降のforceでは、値を再計算する代わりに、保存された値が使用されます。
これは、何らかのコードが何らかの状況で値を期待しているものの、その値が本当に必要となるかどうかを前もって知ることができないような場合に便利です。プロミスを使って最適化を行うためには、期待する側のコードでは、当然、値ではなくプロミスを期待するようにします。プロミスが値を得るためにはまず「force」しなければならないからです。しかしその値が必要となるか、あるいはどのコードが必要とするかを最初に考える必要はありません。実行時に判明することです。
プロミスの生成には4つの形があります。lazyという形が最もゆるい方法です。
my $red = foo($arg);
my $value = lazy {
$green = green($red);
$blue = blue($red, $green);
}
すでに名前のあるルーチンの評価を送らせたい場合にはdelayが便利です。
my $value = delay \&costly_function;
ただし、この例の中のcostly_functionは、サンクでなければならない、つまり引数を期待してはいけないということに注意してください。通常は、次のようにクロージャーを使うか、またはlazyを使って引数をカプセル化することになります。
my $value1 = lazy { fibonacci(8675309) };
これは、(a) $value1をforceしなければならないこと、(b) 8675309番目のfibonacciはforceされない限り計算されないこと、の2点を除いて、$value2 = fibonacci(865309)と書くこととほとんど同じです。
最後に、補足になりますが、プロミスの生成には、他のメソッドの場合より見栄えは悪くなりますが、次のようにnewを使うことができます。
my $value = new MT::Promise(sub { fibonacci(8675309) });
何らかの理由でプロミスをサブクラス化するときにはこの方法が便利です。
次のようにlazyのブロックの外側で変数名を宣言した場合、
my $red;
my $value = lazy { foo($red) }
$valueは、メモリーから解放されるまで$redへのリファレンスを持ち続けることになります。このことを忘れていると、気づかないうちに循環リファレンスを作ってしまうことがあるので注意が必要です。