まずは MonadPlus ばんじゃーいとこういう感じに mzero
と mplus
を定義してみる. mzero
が関数になっているのは TypeScript ではこうしないと (関数以外の値を) パラメータ多相にできないため. ちなみに Parser<A>
の A
は結果の値の型で, Parser
はこれについて共変.
declare const mzero: <A>() => Parser<A>; declare const mplus: <A>(parserA: Parser<A>, parserB: Parser<A>) => Parser<A>;
するとこういう感じで, 本当は p2
も Parser<T>
になってほしいのだけれど, 明示的に型パラメータを渡したり注釈を書かない限りは mzero
を適当にインスタンス化するのが優先されて Parser<{}>
になってしまう.
enum T { } // なんでも良い declare const parser: Parser<T>; const p1 = mplus(parser, mzero()); // : Parser<T> const p2 = mplus(mzero(), parser); // : Parser<{}> ← えっあのちょっと
というわけでパラメータ多相をやめて, かわりに subtype 多相を使ってこんなふうに never
と union (|
) を使って定義しなおす.
declare const mzero: Parser<never>; declare const mplus: <A, B>(parserA: Parser<A>, parserB: Parser<B>) => Parser<A | B>;
するとめでたく両方 Parser<T>
になります. 任意の T
に対して T | never = never | T = T
なので never
は消えてなくなる. ついでに mzero
を使うのにいちいち関数呼び出しをする必要もなくなって便利.
const p1 = mplus(parser, mzero); // : Parser<T> const p2 = mplus(mzero, parser); // : Parser<T>
かわりに mplus
の引数として別々の型を返す Parser
を渡して良くなってしまっているわけですが, だからといって結果の型に不整合が出たりするわけではないので特に困ることはないでしょう.
何の話: https://github.com/susisu/loquat/blob/master/packages/simple/index.d.ts