下記のスライド読んでて、enum的なもの以外でもできるのかなと思って少し考えてみた。
もう少しボイラープレート減らせないのかなぁ、とは思うがTypeScriptよく知らないので、私にはこれ以上どうにもならなかった。
なお、TypeScript playgroundでコンパイルエラーがないか確認しただけなので、ちゃんと動くかはわかりません(爆)。
あと、判別共用体やるなら種別を表す共通のプロパティを作ってswitch文で分岐するのが正解っぽい。
type CaseOf = { [K in string]: any[] }
type CaseOfMethod<C extends CaseOf> =
<R>(c: { [K in keyof C]: (...x: C[K]) => R }) => R
// data Maybe a = Nothing | Just a
type CaseOfMaybe<A> = {Nothing:[], Just:[A]}
class Maybe<A> {
private constructor(readonly caseOf: CaseOfMethod<CaseOfMaybe<A>>){}
static readonly Nothing = <A>(...x: CaseOfMaybe<A>["Nothing"]) =>
new Maybe<A>(c => c.Nothing(...x))
static readonly Just = <A>(...x: CaseOfMaybe<A>["Just"]) =>
new Maybe<A>(c => c.Just(...x))
readonly map = <B>(f: (x: A) => B): Maybe<B> => this.caseOf({
Nothing:() => Maybe.Nothing(),
Just:(x) => Maybe.Just(f(x))
})
}
// data List a = Nil | Cons a (List a)
type CaseOfList<A> = {Nil:[], Cons:[A, List<A>]}
class List<A> {
private constructor(readonly caseOf: CaseOfMethod<CaseOfList<A>>){}
static readonly Nil = <A>(...x: CaseOfList<A>["Nil"]) =>
new List<A>(c => c.Nil(...x))
static readonly Cons = <A>(...x: CaseOfList<A>["Cons"]) =>
new List<A>(c => c.Cons(...x))
readonly map = <B>(f: (x: A) => B): List<B> => this.caseOf({
Nil:() => List.Nil(),
Cons:(x, xs) => List.Cons(f(x), xs.map(f))
})
}
// data Bool = False | True
type CaseOfBool = {False:[], True:[]}
class Bool {
private constructor(readonly caseOf: CaseOfMethod<CaseOfBool>){}
static readonly False = (...x: CaseOfBool["False"]) =>
new Bool(c => c.False(...x))
static readonly True = (...x: CaseOfBool["True"]) =>
new Bool(c => c.True(...x))
readonly and = (other: Bool): Bool => this.caseOf({
False:() => this,
True:() => other
})
readonly or = (other: Bool): Bool => this.caseOf({
False:() => other,
True:() => this
})
readonly not = ():Bool => this.caseOf({
False:() => Bool.True(),
True:() => Bool.False()
})
}