Slide 1

Slide 1 text

美しいコードを書くために F# を学んでみた話 0yu @yud0uhu 2026/07/12(Sun) 関数型まつり

Slide 2

Slide 2 text

@yud0uhu @yud0uhu 0yu(おゆ) Web FE 普段はTypeScriptを書きます 好きなもの 推理小説、謎解き・マダミス、ウイ スキー

Slide 3

Slide 3 text

Agenda Why F#? 他言語学習者がF#を学ぶモチベーション Functional types as an idiom in TypeScript Branded Typeで制約を表現する Discipline the compiler enforces 型でドメインを表現するということ

Slide 4

Slide 4 text

Why F#? 他言語学習者がF#を学ぶモチベーション

Slide 5

Slide 5 text

「品質の良いコード」 を書きたい 🤔💭

Slide 6

Slide 6 text

「品質の良いコード」 とは何か メンテナブル(時間軸で見た時に腐らない) 意図が明確(誤用できない設計) → 品質の良いコードは設計が美しい

Slide 7

Slide 7 text

「品質の良いコード」 とは何か メンテナブル(時間軸で見た時に腐らない) 意図が明確(誤用できない設計) → 品質の良いコードは設計が美しい

Slide 8

Slide 8 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 三種類のoptionalなpropsで状態を表現する type Props = { isLoading?: boolean; error?: string; data?: User[]; }; function UserList({ isLoading, error, data }: Props) { if (isLoading) return ; if (error) return ; return ; } // ありえない状態が通ってしまう

Slide 9

Slide 9 text

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Discriminated Unionで状態を表現する type RemoteData = | { status: "loading" } | { status: "error"; message: string } | { status: "success"; data: T }; const UserList = ({ state }: { state: RemoteData }) => { switch (state.status) { case "loading": return ; case "error": return ; case "success": return ; } }; // ありえない状態を型で弾いてくれる

Slide 10

Slide 10 text

「設計が美しいコード」 とは何が「美しい」のか 🤔💭

Slide 11

Slide 11 text

「設計が美しいコード」 とは何が「美しい」のか ありえない状態が、そもそも表現できないこと → コンパイラ (型) に強制させたい

Slide 12

Slide 12 text

「設計が美しいコード」 とは何が「美しい」のか ありえない状態が、そもそも表現できないこと → コンパイラ (型) に強制させたい

Slide 13

Slide 13 text

型があると 何が嬉しいのか 🤔💭

Slide 14

Slide 14 text

そもそも プログラミングにおける「型」って何だろ う? 🤔💭

Slide 15

Slide 15 text

型とは「値の集合」

Slide 16

Slide 16 text

型のメンタルモデル 1 2 3 4 5 6 7 8 9 10 11 // ふたつの集合の和集合を表現するユニオン型と、 // ふたつの型の共通部分を表現するインターセクション型 type A = { a: string }; type B = { b: number };   // AとBの和集合を表現する型 type Union = A | B;   // AとBの共通部分を表現する型 type Intersection = A & B; 出典:型のメンタルモデル | TypeScript入門『サバイバルTypeScript』 https://typescriptbook.jp/reference/values-types-variables/mental-model-of-types

Slide 17

Slide 17 text

型を集合として捉える neverは空集合 リテラル型は単集合 unknownは全体集合 1 2 3 4 5 6 7 8 9 10 11 // どの値も属さない集合 type OrderStatus = "draft" | "paid" | "shipped"; switch (status) { case "draft": ... case "paid": ... case "shipped": ... default: const rest: never = status; // + type OrderStatus = ... | "cancelled"; // Type '"cancelled"' is not assignable to type 'never'. } 1 2 3 4 5 6 // 値がちょうど1つの集合 type Draft = "draft"; // 集合に属している const s1: Draft = "draft"; // 集合に属さない const s2: Draft = "paid"; 1 2 3 4 // 値がちょうど1つ type Red = "red"; const ok: Red = "red"; // ✅ const ng: Red = "blue"; // ❌ "blue" は集合に属さない 1 2 3 4 // すべての値を含む集合 const res: unknown = await fetchOrderStatus(); // 絞り込むまで使えない res.toUpperCase();

Slide 18

Slide 18 text

参考資料 型を集合として捉える https://x.com/farstep_/article/2069194422995313135

Slide 19

Slide 19 text

ありえない状態を、型で表現できなくする
 → この考え方はどこで磨かれてきたのか?

Slide 20

Slide 20 text

ありえない状態を、型で表現できなくする
 → この考え方はどこで磨かれてきたのか?

Slide 21

Slide 21 text

関数型言語のドメインモデリングの中核に あるのは代数的データ型

Slide 22

Slide 22 text

💡 関数型言語を学ぼう

Slide 23

Slide 23 text

参考資料 関数型ドメインモデリング
 ドメイン駆動設計とF#でソフトウェアの複雑さに立ち向かおう

 Scott Wlaschin (著), 猪股 健太郎 (翻訳)
 出版社:KADOKAWA https://www.kadokawa.co.jp/product/302405003608/

Slide 24

Slide 24 text

Functional types as an idiom in TypeScript Branded Typeで制約を表現する

Slide 25

Slide 25 text

ドメインモデルのパターンを見る 単純な値 文字列や整数など、プリミティブ 型で表される基本的な構成要素 intやstringではなく、注文IDや製 品コードといったユビキタス言語 の概念で考える ANDによる値の組み合わせ / ORによる選択肢 AND 密接に関連したデータのグループ 名前、住所、注文など OR 選択肢を表すもの 注文または見積 ユニット数またはキログラム量 ワークフロー インプットとアウトプットを持つ
 ビジネスプロセス

Slide 26

Slide 26 text

F# の型によるドメインモデリング

Slide 27

Slide 27 text

Functional types as an idiom in F# ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 製品コード関連 type WidgetCode = WidgetCode of string // 制約: 先頭が"W" + 数字4桁 type GizmoCode = GizmoCode of string // 制約: 先頭が"G" + 数字3桁 type ProductCode = | Widget of WidgetCode | Gizmo of GizmoCode // 注文数量関連 type UnitQuantity = UnitQuantity of int // 個数 type KilogramQuantity = KilogramQuantity of decimal //重さ type OrderQuantity = | Unit of UnitQuantity | Kilogram of KilogramQuantity // 未定義プレースホルダ // 設計上のTODOを「Undefined」でコンパイラに管理してもらう type OrderId = Undefined type OrderLineId = Undefined type CustomerId = Undefined

Slide 28

Slide 28 text

Functional types as an idiom in F# ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 注文とその構成要素 type CustomerInfo = Undefined type ShippingAddress = Undefined type BillingAddress = Undefined type Price = Undefined type BillingAmount = Undefined type Order = { Id : OrderId CustomerId : CustomerId ShippingAddress : ShippingAddress BillingAddress : BillingAddress OrderLines : OrderLine list AmountToBill : BillingAmount } and OrderLine = { Id : OrderLineId OrderId : OrderId ProductCode : ProductCode OrderQuantity : OrderQuantity Price : Price }

Slide 29

Slide 29 text

Functional types as an idiom in F# ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 未検証の注文を型で宣言する type UnvalidatedOrder = { OrderId : string CustomerInfo : ... ShippingAddress : ... ... } // ワークフローの出力 type PlaceOrderEvents = { AcknowledgmentSent : ... OrderPlaced : ... BillableOrderPlaced : ... } type PlaceOrderError = | ValidationError of ValidationError list | ...その他のエラー and ValidationError = { FieldName : string ErrorDescription : string } /// 「注文確定」プロセス type PlaceOrder = UnvalidatedOrder -> Result

Slide 30

Slide 30 text

Functional types as an idiom in F# ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 未検証の注文を型で宣言する type UnvalidatedOrder = { OrderId : string CustomerInfo : ... ShippingAddress : ... ... } // ワークフローの出力 type PlaceOrderEvents = { AcknowledgmentSent : ... OrderPlaced : ... BillableOrderPlaced : ... } type PlaceOrderError = | ValidationError of ValidationError list | ...その他のエラー and ValidationError = { FieldName : string ErrorDescription : string } /// 「注文確定」プロセス type PlaceOrder = UnvalidatedOrder -> Result type Result<'T,'TError> = | Ok of 'T | Error of 'TError

Slide 31

Slide 31 text

TypeScript の型によるドメインモデリング

Slide 32

Slide 32 text

TypeScriptの型システム
 →「構造的部分型(structural typing)」 →中身の形が同じなら同じ型と判定する

Slide 33

Slide 33 text

TypeScriptの型システム
 →「構造的部分型(structural typing)」 →中身の形が同じなら同じ型と判定する

Slide 34

Slide 34 text

TypeScriptの型システム
 →「構造的部分型(structural typing)」 →中身の形が同じなら同じ型と判定する

Slide 35

Slide 35 text

1 2 3 4 5 6 type WidgetCode = string; type GizmoCode = string; const w: WidgetCode = "W1234"; // 構造が同じ (互換性が保たれていると見なされる) のため代入可能 const g: GizmoCode = w;

Slide 36

Slide 36 text

Branded Types → 型を区別するためのプロパティを型に持た せることで、構造的に同じな型同士を区別す る仕組み

Slide 37

Slide 37 text

Branded Types → 型を区別するためのプロパティを型に持た せることで、構造的に同じな型同士を区別す る仕組み

Slide 38

Slide 38 text

1 2 3 4 5 6 7 8 9 10 type WidgetCode = string & { readonly __brand: "WidgetCode"; }; type GizmoCode = string & { readonly __brand: "GizmoCode"; }; const w: WidgetCode = "W1234"; // __brandの中身が異なる ("WidgetCode" ≠ "GizmoCode") ため代入不可 const g: GizmoCode = w;

Slide 39

Slide 39 text

Branded Types は型システム上の紳士協定 🤔💭

Slide 40

Slide 40 text

1 2 3 4 5 6 7 8 9 10 11 12 13 // 代入可能 const w: WidgetCode = "W1234" as WidgetCode; // 代入可能 const fake: GizmoCode = "banana" as GizmoCode; // 代入不可能 const g: GizmoCode = w; // error TS2322: Type 'WidgetCode' is not assignable to type 'GizmoCode'. // Type 'WidgetCode' is not assignable to type '{ readonly __brand: "GizmoCode"; }'. // Types of property '__brand' are incompatible. // Type '"WidgetCode"' is not assignable to type '"GizmoCode"'.

Slide 41

Slide 41 text

参考資料 AI時代に考える、Branded Typesで実現する堅牢な型付け
 @yuta-ike https://2026.tskaigi.org/talks/62

Slide 42

Slide 42 text

参考資料 スプレッド構文によるブランド流出問題を乗り越えて、
 オブジェクト型に対する Branded Types を使い倒す
 @tony https://www.docswell.com/s/tony998244353/KMQVLG-2026-05-23-123609#p1

Slide 43

Slide 43 text

Functional types as an idiom in TypeScript ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 製品コード関連 type WidgetCode = string & { readonly __brand: "WidgetCode"; }; // W + 数字4桁 type GizmoCode = string & { readonly __brand: "GizmoCode"; }; // G + 数字3桁 type ProductCode = | { kind: "widget"; code: WidgetCode; } | { kind: "gizmo"; code: GizmoCode; };

Slide 44

Slide 44 text

Functional types as an idiom in TypeScript ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 注文数量関連 type UnitQuantity = number & { readonly __brand: "UnitQuantity"; }; type KilogramQuantity = number & { readonly __brand: "KilogramQuantity"; }; type OrderQuantity = | { kind: "unit"; value: UnitQuantity; } | { kind: "kilogram"; value: KilogramQuantity; }; // 未定義プレースホルダ type Undefined = never;

Slide 45

Slide 45 text

Functional types as an idiom in TypeScript ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 注文とその構成要素 type CustomerInfo = Undefined; type ShippingAddress = Undefined; type BillingAddress = Undefined; type Price = Undefined; type BillingAmount = Undefined; type OrderId = string & { readonly __brand: "OrderId"; }; type CustomerId = string & { readonly __brand: "CustomerId"; }; type OrderLineId = string & { readonly __brand: "OrderLineId"; }; type Order = { readonly id: OrderId; readonly customerId: CustomerId; readonly shippingAddress: ShippingAddress; readonly billingAddress: BillingAddress; readonly orderLines: readonly OrderLine[]; readonly amountToBill: BillingAmount; }; type OrderLine = { readonly id: OrderLineId; readonly orderId: OrderId; readonly productCode: ProductCode; readonly orderQuantity: OrderQuantity; readonly price: Price; };

Slide 46

Slide 46 text

Functional types as an idiom in TypeScript ドメインモデリングを型で表現する 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 未検証の注文を型で宣言する type UnvalidatedOrder = { readonly orderId: string; readonly customerInfo: Undefined; readonly shippingAddress: Undefined; // ... }; // ワークフローの出力 type PlaceOrderEvents = { readonly acknowledgmentSent: Undefined; readonly orderPlaced: Undefined; readonly billableOrderPlaced: Undefined; }; // エラーはOR型(discriminated union)で列挙する type ValidationError = { readonly fieldName: string; readonly errorDescription: string; }; type PlaceOrderError = | { kind: "validation"; errors: readonly ValidationError[]; } | { kind: "other"; message: string }; type Result = | { ok: true; value: T; } | { ok: false; error: E };

Slide 47

Slide 47 text

Functional types as an idiom in TypeScript ドメインモデリングを型で表現する 1 2 3 /** 「注文確定」プロセス */ type PlaceOrder = (order: UnvalidatedOrder) => Result;

Slide 48

Slide 48 text

余談:Proposal Try Operator

Slide 49

Slide 49 text

Discipline the compiler enforces 型でドメインを表現するということ

Slide 50

Slide 50 text

ドメインの要件を型システムに組み込めたら 型がドキュメントの代わりになる → 型はコンパイラが検査してくれる → LTV (Life Time Value) の高いコード

Slide 51

Slide 51 text

ドメインの要件を型システムに組み込めたら 型がドキュメントの代わりになる → 型はコンパイラが検査してくれる → LTV (Life Time Value) の高いコード

Slide 52

Slide 52 text

ドメインの要件を型システムに組み込めたら 型がドキュメントの代わりになる → 型はコンパイラが検査してくれる → LTV (Life Time Value) の高いコード

Slide 53

Slide 53 text

🤝 F# で関数型ファーストの作法を学び TS の紳士協定で美しいコードを書こう