Slide 1

Slide 1 text

TypeScript における 型レベルバリデーション チェシャ猫 (@y_taka_23) We Are JavaScripters! @36th (2019/09/30) #wejs

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

TypeScript の型が保証するもの ● 値の「種類」は型が保証 ○ 生の JavaScript に比べればだいぶ助かる ● 値の「内容」は型では保証できない ○ name: string は「空でない」文字列 ○ age: number は「0 以上」かつ「自然数」 ● ロジックのバグに対して意外と無力 ○ できればドメイン制約も強制したい #wejs

Slide 4

Slide 4 text

もっと型に情報を載せたい #wejs

Slide 5

Slide 5 text

よくある手法:幽霊型 #wejs

Slide 6

Slide 6 text

幽霊型 (Phantom Types) ● 型にパラメータを追加 ○ Generics でパラメータ付きの型を作る ○ ただし、その型は実際には使用しない ● 幽霊型の使いどころ ○ 実行時に同じデータ (ex. string) であっても、 型検査されたときに異なる型として振る舞う ○ バリデーションの通過を型で表現できる #wejs

Slide 7

Slide 7 text

interface StringOf { value: string } const UserName = "UserName" type UserName = typeof UserName interface User { name: StringOf age: number } function validateName(s: string): StringOf { // validation logic here return { value: s } } #wejs

Slide 8

Slide 8 text

function makeUser( name: StringOf, age: number) { return { name, age } } // compilation error! const badUser = makeUser('John', 20) // no error const goodUser = makeUser(validateName('John'), 20) #wejs

Slide 9

Slide 9 text

バリデーションを型で表現できた (age についても同様) #wejs

Slide 10

Slide 10 text

文字列の種別が増えたら? #wejs

Slide 11

Slide 11 text

const UserId = "UserId" type UserId = typeof UserId Interface User { id: StringOf name: StringOf age: number } function validateId(s: string): StringOf { // another validation logic for UserId return { value: s } } #wejs

Slide 12

Slide 12 text

function makeUser( id: StringOf, name: StringOf, 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

Slide 13

Slide 13 text

(幽霊型、駄目じゃん) #wejs

Slide 14

Slide 14 text

公称型と構造型 ● 公称的部分型 (nominal subtyping) ○ 明示的な宣言によって型を判定 ○ 宣言がない場合は部分型だと見なさない ● 構造的部分型 (structural subtyping) ○ 明示的に部分型関係を宣言しない ○ 必要なフィールドを持つかどうかだけで判定 ● TypeScript は構造的部分型をサポート #wejs

Slide 15

Slide 15 text

interface StringOf { value: string } const myId: StringOf = validateId('u0001') const myName: StringOf = validateName('John') // no error const goodUser = makeUser(myId, myName, 20) // no error... const badUser = makeUser(myName, myId, 20) #wejs

Slide 16

Slide 16 text

フィールドで差を付ける必要性 #wejs

Slide 17

Slide 17 text

Branded Types #wejs

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

まとめ ● 標準の型だとやや非力 ○ ドメイン制約から来る「内容」が保証できない ● 単純な幽霊型は期待通り動作しない ○ 構造的部分型のせいで区別できない型が生じる ● Branded Type を用いると便利 ○ 複数種類の仕様がある場合でも区別できる #wejs

Slide 21

Slide 21 text

Let’s Validate the World! Presented by チェシャ猫 (@y_taka_23) #wejs