言語拡張は以下の2点。
- クラスにoperator doを定義できるようにする
do 型名 { 変数宣言 <- 値; 値; ...}
という構文の新設
使用イメージは以下のような感じ。
// モナドの型
template <typename T> class MyMonad {
MyMonad(const T& t) {...} // Haskellのpure(return)に相当
...
template <typename R, typename F>
MyMonad<R> operator do (F f) {...}
// Fは `function< MyMonad<R> (const T&) >` 。
// Haskellに翻訳すると `(>>=) :: MyMonad T -> (T -> MyMonad R) -> MyMonad R`
// ただし第一引数はthisで与える。
};
// プリミティブコマンド
MyMonad<int> command(int n) {...}
main () {
// モナドの実体の記述
auto m = do MyMonad {
auto x <- command(10);
command(x);
return x;
}
...
}
これは
main() {
auto m = MyMonad<unit_type>( unit_type() ).operator do(
[](unit_type) {
command(10).operator do(
[](auto x) {
return command(x).operator do(
[](auto) {
return MyMonad<decltype(x)>(x);
}
)
}
)
}
);
...
}
に脱糖される。後続の式が [] {}
の中に入れられてoperator do (...) { operator do(...) {...} } の入れ子に変換されるイメージ。
ここで struct unit_type {}; は標準で提供する型。Haskellの()に相当。
本来C++で f :: IO ()
に相当するのは void f()
だが、MyMonadの扱いが煩雑なのでこのようにした。
command(10).operator do から始まっても良いのだが、commandに副作用があった場合に
1行目のみ即座に実行されてしまうという非直感的な動作になる。
そこで、先頭に pure ()
があるものとして脱糖している。モナド則から pure () >> x === x
。
return文が無かった場合 return MyMonad(unit_type); が最後にあるものとして脱糖する。
変数宣言 = 値;
ではなく 変数宣言 <- 値;
としたのは…… int x = MyMonad<int>(10);
みたいに左右の型が合わないのはキモいよねっていう。