型で正しい状態の場合分けを列挙して表現する
Making Illegal States Unrepresentableの具体例
以下の2つは同じ意味ではないよねという話
code:1.ts
type X = {
a: A1 | A2;
b: B1 | B2
}
code:2.ts
type X = { a: A1; b: B1 }
| { a: A2; b: B2 }
仕様として以下のようなものをが許容されないのであれば、2.tsのように定義すべき
code:ts
type Y = {
a: A1
b: B2
}
A1,A2,B1,B2の取りうる値が1つだったとしても、前者は4パターン取りうる
その場合、この型を使った関数は、その引数の型が取りうる値を全てサポートする責任があるので、本来より2倍のことを想定して実装しないといけない
テストのパターンも倍になる
型は仕様なので、それで表現した値は全部許容される、と明言したことになる
仕様にふさわしいのが2.tsなのであればそう書くべき
もちろん1.tsの方がふさわしい仕様の場合もあるmrsekut.icon
例えば、以下のような仕様があるとする
Userは、必ずEmailか電話番号の情報を持っている
この仕様を満たすように型を定義するとどうなるか?
例えば、こう書ける
code:ts
type User = {
name: UserName;
email: Email;
phone: PhoneNumber;
}
しかし、これだと両方必須になってしまうので、
例えばこう修正する
code:ts
type User = {
name: UserName;
email: Email | undefined;
phone: PhoneNumber | undefined;
}
しかし、これも間違い。
この型の場合、emailとphoneの両方がundefinedなものも許容してしまう
code:ts
const user1: User = {
name: 'mrsekut',
email: undefined,
phone: undefined
}
型が仕様を正しく表現していない
例えば、こうする
code:ts
type User = {
name: UserName;
email: Email;
phone: undefined;
} | {
name: UserName;
email: undefined;
phone: PhoneNumber
} | {
name: UserName;
email: Email;
phone: PhoneNumber;
}
Tagged Unionをあまり考慮していないので微妙かもしれんmrsekut.icon
場合分けは、user.email != nullのように、「undefinedじゃない」という条件式でしか絞り込めない
あるいはこうする
Tagged Unionにする
code: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部分のみをそうする
code:ts
type User = {
name: UserName;
contact: Contact;
}
type Contact = {
type: 'emailOnly';
email: Email
} | {
type: 'phoneOnly';
phone: PhoneNumber;
} | {
type: 'both';
email: Email;
phone: PhoneNumber;
}
Haskellならこうするかな
code: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
参考
/mrsekut-book-97816805025/123 (Capturing Business Rules in the Type System) ~
Designing with types ref
Real World Halogen (Draft) ref
正しい状態(自体)をunionする
code:hs
data ContactInfo
= EmailOnly Email
| PostalOnly Postal
| Both Email Postal
仕様的に空になることがないなら、それも型で表現する
code:hs
type Order = { items :: NonEmptyArray Item }