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

美しいコードを書くためにF#を学んでみた話

Avatar for yud0uhu yud0uhu
July 11, 2026

 美しいコードを書くためにF#を学んでみた話

関数型まつり2026 Day 2の資料です。
https://2026.fp-matsuri.org/

Avatar for yud0uhu

yud0uhu

July 11, 2026

More Decks by yud0uhu

Other Decks in Technology

Transcript

  1. Agenda Why F#? 他言語学習者がF#を学ぶモチベーション Functional types as an idiom in

    TypeScript Branded Typeで制約を表現する Discipline the compiler enforces 型でドメインを表現するということ
  2. 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 <Spinner />; if (error) return <ErrorMessage message={error} / >; return <List items={data} />; } // ありえない状態が通ってしまう <UserList isLoading={true} error="失敗" data={users} /> <UserList />
  3. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 // Discriminated Unionで状態を表現する type RemoteData<T> = | { status: "loading" } | { status: "error"; message: string } | { status: "success"; data: T }; const UserList = ({ state }: { state: RemoteData<User[]> }) => { switch (state.status) { case "loading": return <Spinner />; case "error": return <ErrorMessage message={state.message} />; case "success": return <List items={state.data} />; } }; // ありえない状態を型で弾いてくれる <UserList state={{ status: "loading", data: users }} />
  4. 型のメンタルモデル 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
  5. 型を集合として捉える 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();
  6. ドメインモデルのパターンを見る 単純な値 文字列や整数など、プリミティブ 型で表される基本的な構成要素 intやstringではなく、注文IDや製 品コードといったユビキタス言語 の概念で考える ANDによる値の組み合わせ / ORによる選択肢

    AND 密接に関連したデータのグループ 名前、住所、注文など OR 選択肢を表すもの 注文または見積 ユニット数またはキログラム量 ワークフロー インプットとアウトプットを持つ
 ビジネスプロセス
  7. 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
  8. 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 }
  9. 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<PlaceOrderEvents, PlaceOrderError>
  10. 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<PlaceOrderEvents, PlaceOrderError> type Result<'T,'TError> = | Ok of 'T | Error of 'TError
  11. 1 2 3 4 5 6 type WidgetCode = string;

    type GizmoCode = string; const w: WidgetCode = "W1234"; // 構造が同じ (互換性が保たれていると見なされる) のため代入可能 const g: GizmoCode = w;
  12. 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;
  13. 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"'.
  14. 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; };
  15. 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;
  16. 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; };
  17. 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<T, E> = | { ok: true; value: T; } | { ok: false; error: E };
  18. Functional types as an idiom in TypeScript ドメインモデリングを型で表現する 1 2

    3 /** 「注文確定」プロセス */ type PlaceOrder = (order: UnvalidatedOrder) => Result<PlaceOrderEvents, PlaceOrderError>;