generated at
型で正しい状態の場合分けを列挙して表現する



以下の2つは同じ意味ではないよねという話
1.ts
type X = { a: A1 | A2; b: B1 | B2 }
2.ts
type X = { a: A1; b: B1 } | { a: A2; b: B2 }
仕様として以下のようなものをが許容されないのであれば、 2.ts のように定義すべき
ts
type Y = { a: A1 b: B2 }
A1 , A2 , B1 , B2 の取りうる値が1つだったとしても、前者は4パターン取りうる
その場合、この型を使った関数は、その引数の型が取りうる値を全てサポートする責任があるので、本来より2倍のことを想定して実装しないといけない
テストのパターンも倍になる
型は仕様なので、それで表現した値は全部許容される、と明言したことになる
仕様にふさわしいのが 2.ts なのであればそう書くべき
もちろん 1.ts の方がふさわしい仕様の場合もあるmrsekut





例えば、以下のような仕様があるとする
Userは、必ずEmailか電話番号の情報を持っている


この仕様を満たすように型を定義するとどうなるか?
例えば、こう書ける
ts
type User = { name: UserName; email: Email; phone: PhoneNumber; }
しかし、これだと両方必須になってしまうので、
例えばこう修正する
ts
type User = { name: UserName; email: Email | undefined; phone: PhoneNumber | undefined; }
しかし、これも間違い。
この型の場合、emailとphoneの両方がundefinedなものも許容してしまう
ts
const user1: User = { name: 'mrsekut', email: undefined, phone: undefined }
型が仕様を正しく表現していない
例えば、こうする
ts
type User = { name: UserName; email: Email; phone: undefined; } | { name: UserName; email: undefined; phone: PhoneNumber } | { name: UserName; email: Email; phone: PhoneNumber; }
Tagged Unionをあまり考慮していないので微妙かもしれんmrsekut
場合分けは、 user.email != null のように、「undefinedじゃない」という条件式でしか絞り込めない
あるいはこうする
Tagged Unionにする
ts
type User = { type: 'emailOnley'; name: UserName; email: Email; } | { type: 'phoneOnly'; name: UserName; phone: PhoneNumber } | { type: 'both'; name: UserName; email: Email; phone: PhoneNumber; }
あるいはこう
userを丸ごとtagged unionにする必要がないので、Contact部分のみをそうする
ts
type User = { name: UserName; contact: Contact; } type Contact = { type: 'emailOnly'; email: Email } | { type: 'phoneOnly'; phone: PhoneNumber; } | { type: 'both'; email: Email; phone: PhoneNumber; }
Haskellならこうするかな
hs
data User = User { name :: UserName , contact :: Contact } data Contact = EmailOnly Email | PhoneOnly Phone | Both BothContactMethods data BothContactMethods = BothContactMethods Email Phone data Email = Email String data Phone = Phone String




参考
正しい状態(自体)をunionする
hs
data ContactInfo = EmailOnly Email | PostalOnly Postal | Both Email Postal
仕様的に空になることがないなら、それも型で表現する
hs
type Order = { items :: NonEmptyArray Item }