型構成子と型パラメータを持つ関数があれば、とりあえずモナドは定義できるのですが、モナドを構成する型構成子と総称関数達をうまくまとめる機構が欠けている感じはします。
TypeScriptの「まとめる機構」にモジュール(module)ってのがありますね。モジュールで、型や関数をまとめてグループにできます。Maybeモナドを例題にして、モナドの構成要素にまとまりをつけてみます。
なお、Maybeモナドの概念的なことは「アイレンベルグ/ムーア圏 その3:Maybeモナドのとき」に割と詳しく書いてあります。そこで使われている名前は、fmap, just, flatten ではなくて lift, embed, join ですのでご注意。
// Maybeモナド module Maybe { // 型構成子:関手の対象部分 export class Data<X> { private _hasVal: boolean; private _val: X; constructor(hasVal:boolean, val?:X) { this._hasVal = hasVal; this._val = val; } get hasVal() {return this._hasVal;} get val() {return this._val;} } // 定数:値がないことを表すオブジェクト export const None = new Data<any>(false); // マップ関数:関手の射部分 export function fmap<X, Y>(f:(x:X)=>Y) : (mx:Data<X>)=>Data<Y> { return ( (mx:Data<X>)=> (!mx.hasVal)? <Data<Y>>None : new Data<Y>(true, f(mx.val)) ); } // モナド単位 export function just<X>(x:X) : Data<X> { return new Data<X>(true, x); } // モナド乗法 export function flatten<X>(mmx:Data<Data<X>>) : Data<X>{ return ( (!mmx.hasVal)? <Data<X>>None : mmx.val ); } }
次のように使います。
// Maybeデータの生成: just true var mt: Maybe.Data<boolean> = Maybe.just(true); // Maybeデータの操作: not で否定 var mf: Maybe.Data<boolean> = Maybe.fmap((t:boolean)=> !t)(mt); // Maybeデータを入れ子にする var mmf: Maybe.Data<Maybe.Data<boolean>> = Maybe.just(mf); // 入れ子をほどく var mf2: Maybe.Data<boolean> = Maybe.flatten(mmf);
目的が共通なモジュールが一律に備えるべき型や関数の仕様(interfaceのモジュール版)を書けると、型クラスみたいに使えるんですけどね。「Maybeモジュールは、Monad仕様のインスタンス」という感じで、具体的モジュールより一段上の抽象化をサポートできます。入らないかな、そんな機能。