Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScript における型レベルバリデーション #wejs / We Are JavaScripters 36th

y_taka_23
September 30, 2019

TypeScript における型レベルバリデーション #wejs / We Are JavaScripters 36th

We Are JavaScripters! @36th で使用したスライドです。

幽霊型は型を詳細化する手法として有名ですが、TypeScript では構造的部分型の働きにより、ナイーブに移植しただけでは期待通り動作しません。そこで、型で値の種類を判別する手段として、Branded Type を用いた設計について解説します。

イベント概要:https://wajs.connpass.com/event/145639/

y_taka_23

September 30, 2019
Tweet

More Decks by y_taka_23

Other Decks in Technology

Transcript

  1. interface User { name: string age: number } function makeUser(name:

    string, age: number) { return { name, age } } // no error const goodUser = makeUser('John', 42) // no error... const badUser = makeUser('', -42) #wejs
  2. TypeScript の型が保証するもの • 値の「種類」は型が保証 ◦ 生の JavaScript に比べればだいぶ助かる • 値の「内容」は型では保証できない

    ◦ name: string は「空でない」文字列 ◦ age: number は「0 以上」かつ「自然数」 • ロジックのバグに対して意外と無力 ◦ できればドメイン制約も強制したい #wejs
  3. 幽霊型 (Phantom Types) • 型にパラメータを追加 ◦ Generics でパラメータ付きの型を作る ◦ ただし、その型は実際には使用しない

    • 幽霊型の使いどころ ◦ 実行時に同じデータ (ex. string) であっても、 型検査されたときに異なる型として振る舞う ◦ バリデーションの通過を型で表現できる #wejs
  4. interface StringOf<T> { value: string } const UserName = "UserName"

    type UserName = typeof UserName interface User { name: StringOf<UserName> age: number } function validateName(s: string): StringOf<UserName> { // validation logic here return { value: s } } #wejs
  5. function makeUser( name: StringOf<UserName>, age: number) { return { name,

    age } } // compilation error! const badUser = makeUser('John', 20) // no error const goodUser = makeUser(validateName('John'), 20) #wejs
  6. const UserId = "UserId" type UserId = typeof UserId Interface

    User { id: StringOf<UserId> name: StringOf<UserName> age: number } function validateId(s: string): StringOf<UserId> { // another validation logic for UserId return { value: s } } #wejs
  7. function makeUser( id: StringOf<UserId>, name: StringOf<UserName>, age: number) { return

    { id, name, age } } const myId = validateId('u0001') const myName = validateName('John') // no error const goodUser = makeUser(myId, myName, 20) // no error... const badUser = makeUser(myName, myId, 20) #wejs
  8. 公称型と構造型 • 公称的部分型 (nominal subtyping) ◦ 明示的な宣言によって型を判定 ◦ 宣言がない場合は部分型だと見なさない •

    構造的部分型 (structural subtyping) ◦ 明示的に部分型関係を宣言しない ◦ 必要なフィールドを持つかどうかだけで判定 • TypeScript は構造的部分型をサポート #wejs
  9. interface StringOf<T> { value: string } const myId: StringOf<UserId> =

    validateId('u0001') const myName: StringOf<UserName> = validateName('John') // no error const goodUser = makeUser(myId, myName, 20) // no error... const badUser = makeUser(myName, myId, 20) #wejs
  10. type UserId = string & { readonly _UserIdBrand: unique symbol

    } type UserName = string & { readonly _UserNameBrand: unique symbol } function validateId(s: string): UserId { // validation logic for UserId return s as UserId } function validateName(s: string): UserName { // validation logic for UserName return s as UserName } #wejs
  11. function makeUser( id: UserId, name: UserName, age: number) { return

    { id, name, age } } const myId = validateId('u0001') const myName = validateName('John') // no error const goodUser = makeUser(myId, myName, 20) // compilation error! const badUser = makeUser(myName, myId, 20) #wejs