subtype 多相, never, union を有効活用していこうな

まずは MonadPlus ばんじゃーいとこういう感じに mzeromplus を定義してみる. mzero が関数になっているのは TypeScript ではこうしないと (関数以外の値を) パラメータ多相にできないため. ちなみに Parser<A>A は結果の値の型で, Parser はこれについて共変.

declare const mzero: <A>() => Parser<A>;
declare const mplus: <A>(parserA: Parser<A>, parserB: Parser<A>) => Parser<A>;

するとこういう感じで, 本当は p2Parser<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