コンピュテーション式は制御フローを簡潔に記述するためのマクロです。いわゆるモナドと呼ばれるものを扱うための構文を提供します。しかしコンピュテーション式はコンピュテーション式であって,モナドを扱うための便利な構文ではありますが,モナドを扱わないといけないということではありません。本文ではモナドについての説明を放棄し,コンピュテーション式をコンテナ型を扱うための構文と捉えて記述していきます。コンテナ型を M[_] のように書けば,コンピュテーション式は M の中で行われる手続きを記述するための構文です。
コンピュテーション式に関するクラスやマクロは Nemerle.ComputationExpressions.dll と Nemerle.ComputationExpressions.Macros.dll に定義されています。マクロ参照に Nemerle.ComputationExpressions.Macros.dll を,通常の参照に Nemerle.ComputationExpressions.dll を追加する必要があります[A]。
コンピュテーション式は,ビルダークラスと呼ばれる特定のメソッドを持つクラスを定義することで,コンピュテーション式の共通の構文を,コンパイル時にビルダークラスのメソッドを利用する形に展開します。以下で見ていきます。
構文
コンピュテーション式は以下のような形式をとります。
comp ビルダークラスのインスタンス
{
式本体
}
より具体的な例を挙げると,以下のようになります。
def nlist = NListBuilder();
def result = comp nlist
{
defcomp x = $[1..9];
defcomp y = $[1..9];
return x * y;
}
例で出てきた defcomp や return といったキーワードは,ビルダークラス (ここでは NListBuilder) の対応するメソッドに展開されます。式本体で利用される構文は次の通りです。
| キーワード | 説明 |
|---|---|
defcomp |
コンテナの中身に変数を束縛します。 |
callcomp |
コンテナの中身に対する操作を行います。 |
return,yield |
コンテナの中身を指定し,コンテナを返します。 |
returncomp,yieldcomp |
コンテナの中身を指定し,その中身を返します。 |
if,when,unless,while,do .. while,repeatforeach,for,using,try .. catch .. finally |
コンテナ内での各操作を表します。 |
ビルダークラスの定義
ビルダークラスを定義するには,特定のシグネチャを持つメソッド[B]をいくつか定義するだけです。具体的には以下のメソッドを定義します。
| メソッド | 対応するキーワード | コメント |
|---|---|---|
Bind[T, U](M[T], T -> M[U]) : M[U] |
defcomp |
|
Bind[T, U](M[T], void -> M[U]) : M[U] |
callcomp |
|
Combine[T](M[T], M[T]) : M[T] |
yield, return |
結合。 |
Delay[T](void -> M[T]) : M[T] |
遅延実行。 | |
ForEach[T, U](Seq[T], T -> M[U]) : M[U] |
foreach |
|
Return[T](T) : M[T] |
return |
|
ReturnComp[T](M[T]) : M[T] |
returncomp |
|
Run[T, U](M[T] -> U) : U |
コンピュテーション式の実行。 | |
TryCatch[T](M[T], Exception -> M[T]) : M[T] |
try .. catch |
|
TryFinally[T](M[T], void -> void) : M[T] |
try .. finally |
|
Using[T, U](M[T], T -> M[U]) : M[U] where U : IDisposable |
using |
|
While[T](void -> bool, M[T]) : M[T] |
while, do .. while |
|
Yield[T](T) : M[T] |
yield |
|
YieldComp[T](M[T]) : M[T] |
yieldcomp |
|
Zero[T]() : M[T] |
when, unless, foreach, while |
処理中で void が返される場合に対応。 |
利用するキーワードに応じて必要なメソッドを定義すれば良いでしょう[C]。
たとえば最初の例の NListBuilder を次のように実装すれば, result は九九の結果になります。
public class NListBuilder
{
public Bind[T, U](m : list[T], f : T -> list[U]) : list[U]
{
m.Map(f).Concat();
}
public Return[T](x : T) : list[T]
{
[x];
}
}
// 再掲
def nlist = NListBuilder();
def result = comp nlist
{
defcomp x = $[1..9];
defcomp y = $[1..9];
return x * y;
}
以下のように展開されます。
nlist.Bind(
$[1..9],
x => nlist.Bind(
$[1..9],
y => nlist.Return(x * y)
)
)