Skip to content

関数型デザイン - 多言語実装ガイド

本ドキュメントは、Clojure 版の記事を他の言語(Scala, TypeScript, Rust, Go など)で実装するための手順書です。

概要

プロジェクト構造

docs/article/
├── clojure/           # オリジナル(参照元)
│   ├── index.md
│   ├── 01-immutability-and-data-transformation.md
│   ├── ...
│   └── 22-oo-to-fp-migration.md
├── scala/             # 実装済み
│   ├── index.md
│   ├── 01-immutability-and-data-transformation.md
│   ├── ...
│   └── 22-oo-to-fp-migration.md
└── {language}/        # 新規実装対象
    ├── index.md
    └── ...

apps/
├── clojure/           # Clojure 実装
├── scala/             # Scala 実装
│   ├── part1/         # 第1部: 基礎原則
│   ├── part2/         # 第2部: 仕様とテスト
│   ├── part3/         # 第3部: 構造パターン
│   ├── part4/         # 第4部: 振る舞いパターン
│   ├── part5/         # 第5部: 生成パターン
│   ├── part6/         # 第6部: ケーススタディ
│   └── part7/         # 第7部: まとめと応用
└── {language}/        # 新規実装対象

章構成(全22章)

タイトル
1 1 不変性とデータ変換
1 2 関数合成と高階関数
1 3 多態性の実現方法
2 4 データ検証(Clojure: Spec)
2 5 プロパティベーステスト
2 6 TDD と関数型
3 7 Composite パターン
3 8 Decorator パターン
3 9 Adapter パターン
4 10 Strategy パターン
4 11 Command パターン
4 12 Visitor パターン
5 13 Abstract Factory パターン
5 14 Abstract Server パターン
6 15 ゴシップ好きなバスの運転手
6 16 給与計算システム
6 17 レンタルビデオシステム
6 18 並行処理システム
6 19 Wa-Tor シミュレーション
7 20 パターン間の相互作用
7 21 ベストプラクティス
7 22 OO から FP への移行

Phase 1: 環境構築

1.1 ディレクトリ作成

# ドキュメントディレクトリ
mkdir -p docs/article/{language}

# コード実装ディレクトリ
mkdir -p apps/{language}/part{1..7}

1.2 プロジェクト設定

各言語のビルドツールとテストフレームワークを設定:

言語 ビルドツール テストフレームワーク
TypeScript npm/pnpm Jest / Vitest
Rust Cargo built-in test
Go go mod built-in testing
Kotlin Gradle JUnit5 / Kotest
F# dotnet xUnit / Expecto
Haskell Cabal/Stack HSpec / QuickCheck
Elixir Mix ExUnit

1.3 Nix 環境(推奨)

flake.nix に新言語の devShell を追加:

{language} = pkgs.mkShell {
  buildInputs = with pkgs; [
    # 言語固有のツール
  ];
  shellHook = ''
    echo "{Language} development environment"
  '';
};

Phase 2: 実装手順(章ごと)

2.1 基本フロー

1. Clojure 記事を読む
   ↓
2. 言語のイディオムにマッピング
   ↓
3. コード実装(TDD)
   ↓
4. テスト作成・実行
   ↓
5. ドキュメント作成
   ↓
6. mkdocs.yml 更新
   ↓
7. Git コミット

2.2 各章の実装

Step 1: Clojure 記事の確認

# 対象章を確認
cat docs/article/clojure/{chapter}.md

Step 2: コード実装

# Part ディレクトリで作業
cd apps/{language}/part{n}

# ソースファイル作成
# 例: src/{ChapterName}.{ext}

# テストファイル作成
# 例: test/{ChapterName}Test.{ext} または {ChapterName}.test.{ext}

Step 3: テスト実行

# 言語ごとのテストコマンド
# TypeScript: npm test
# Rust: cargo test
# Go: go test ./...

Step 4: ドキュメント作成

# Clojure をベースに言語固有の記事を作成
# テンプレート: docs/article/scala/{chapter}.md を参考

Step 5: mkdocs.yml 更新

nav:
  - {Language}:
      - 概要: {language}/index.md
      - 第1部 - 基礎原則:
          - 1. 不変性とデータ変換: {language}/01-immutability-and-data-transformation.md
      # ... 続く

Step 6: コミット

git add apps/{language}/part{n}/ docs/article/{language}/{chapter}.md mkdocs.yml
git commit -m "Add Chapter {N}: {Title} in {Language}

- Implement {MainComponent}.{ext}
- {X} tests for all components"

Phase 3: 言語間マッピング

3.1 基本概念のマッピング

Clojure Scala TypeScript Rust Go
defn def function / const fn func
let val const let :=
atom AtomicReference let mut / Ref Mutex / RwLock sync.Mutex
defrecord case class interface / type struct struct
defprotocol trait interface trait interface
defmulti pattern match type guards match type switch
-> (threading) method chain pipe (|>) method chain method chain
map .map .map .map / .iter().map() for loop
reduce .foldLeft .reduce .fold for loop
filter .filter .filter .filter for loop

3.2 ADT(代数的データ型)

// Scala
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
// TypeScript
type Shape = 
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number };
// Rust
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}
// Go (interface-based)
type Shape interface {
    Area() float64
}

type Circle struct {
    Radius float64
}

type Rectangle struct {
    Width, Height float64
}

3.3 パターンマッチ

// Scala
def area(shape: Shape): Double = shape match
  case Circle(r) => Math.PI * r * r
  case Rectangle(w, h) => w * h
// TypeScript
function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2;
    case 'rectangle': return shape.width * shape.height;
  }
}
// Rust
fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
        Shape::Rectangle { width, height } => width * height,
    }
}

3.4 不変性の実現

言語 アプローチ
TypeScript readonly, as const, Immer
Rust デフォルトで不変、mut で可変
Go 値渡し、コピー
Kotlin val, data class, copy()
F# デフォルトで不変

Phase 4: ダイアグラム

4.1 PlantUML を使用

すべてのダイアグラムは PlantUML で記述:

<img src="data:image/svg+xml;base64,PD9wbGFudHVtbCAxLjIwMjYuMT8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgaGVpZ2h0PSIyOTlweCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgc3R5bGU9IndpZHRoOjM3M3B4O2hlaWdodDoyOTlweDtiYWNrZ3JvdW5kOiMwMDAwMDA7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNzMgMjk5IiB3aWR0aD0iMzczcHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzLz48Zz48cmVjdCBmaWxsPSIjMUMwOTE3IiBoZWlnaHQ9IjEiIHN0eWxlPSJzdHJva2U6IzFDMDkxNztzdHJva2Utd2lkdGg6MTsiIHdpZHRoPSIxIiB4PSIwIiB5PSIwIi8+PHRleHQgZmlsbD0iIzMzRkYwMiIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtc3R5bGU9Iml0YWxpYyIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iMTI4LjAwOTgiIHg9IjUiIHk9IjE3Ij5QbGFudFVNTCAxLjIwMjYuMTwvdGV4dD48cmVjdCBmaWxsPSIjMzNGRjAyIiBoZWlnaHQ9IjIxLjI5NjkiIHN0eWxlPSJzdHJva2U6IzMzRkYwMjtzdHJva2Utd2lkdGg6MTsiIHdpZHRoPSIyMzMuNTM2MSIgeD0iNSIgeT0iMjYuOTY4OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxODEuMDIyNSIgeD0iNiIgeT0iNDEuOTY4OCI+W0Zyb20gc3RyaW5nIChsaW5lIDEzKSBdPC90ZXh0Pjx0ZXh0IGZpbGw9IiMzM0ZGMDIiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI0Ljg3NCIgeD0iNSIgeT0iNjIuMjY1NiI+JiMxNjA7PC90ZXh0Pjx0ZXh0IGZpbGw9IiMzM0ZGMDIiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI4MS40MjI5IiB4PSI1IiB5PSI3OC41NjI1Ij5Ac3RhcnR1bWw8L3RleHQ+PHRleHQgZmlsbD0iIzMzRkYwMiIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEwNy4zNTEzIiB4PSI1IiB5PSI5NC44NTk0Ij50aXRsZSAmIzEyNDk3OyYjMTI0Nzk7JiMxMjU0MDsmIzEyNTMxOyYjMjE1MTc7PC90ZXh0Pjx0ZXh0IGZpbGw9IiMzM0ZGMDIiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI0Ljg3NCIgeD0iNSIgeT0iMTExLjE1NjMiPiYjMTYwOzwvdGV4dD48dGV4dCBmaWxsPSIjMzNGRjAyIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iMjMzLjUzNjEiIHg9IjUiIHk9IjEyNy40NTMxIj5pbnRlcmZhY2UgIkNvbXBvbmVudCIgYXMgQyB7PC90ZXh0Pjx0ZXh0IGZpbGw9IiMzM0ZGMDIiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxNjEuNjI4OSIgeD0iNSIgeT0iMTQzLjc1Ij4rb3BlcmF0aW9uKCk6IFJlc3VsdDwvdGV4dD48dGV4dCBmaWxsPSIjMzNGRjAyIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iOS45NjY4IiB4PSI1IiB5PSIxNjAuMDQ2OSI+fTwvdGV4dD48dGV4dCBmaWxsPSIjMzNGRjAyIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNC44NzQiIHg9IjUiIHk9IjE3Ni4zNDM4Ij4mIzE2MDs8L3RleHQ+PHRleHQgZmlsbD0iIzMzRkYwMiIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjE3OC42MTYyIiB4PSI1IiB5PSIxOTIuNjQwNiI+Y2xhc3MgIkNvbmNyZXRlQSIgYXMgQTwvdGV4dD48dGV4dCBmaWxsPSIjMzNGRjAyIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iMTc4LjI4ODEiIHg9IjUiIHk9IjIwOC45Mzc1Ij5jbGFzcyAiQ29uY3JldGVCIiBhcyBCPC90ZXh0Pjx0ZXh0IGZpbGw9IiMzM0ZGMDIiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI0Ljg3NCIgeD0iNSIgeT0iMjI1LjIzNDQiPiYjMTYwOzwvdGV4dD48dGV4dCBmaWxsPSIjMzNGRjAyIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNTguMzM3OSIgeD0iNSIgeT0iMjQxLjUzMTMiPkMgJmx0O3wuLiBBPC90ZXh0Pjx0ZXh0IGZpbGw9IiMzM0ZGMDIiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI1OC4xNzM4IiB4PSI1IiB5PSIyNTcuODI4MSI+QyAmbHQ7fC4uIEI8L3RleHQ+PHRleHQgZmlsbD0iIzMzRkYwMiIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHQtZGVjb3JhdGlvbj0id2F2eSB1bmRlcmxpbmUiIHRleHRMZW5ndGg9IjIxIiB4PSI1IiB5PSIyNzQuMTI1Ij5gYGA8L3RleHQ+PHRleHQgZmlsbD0iI0ZGMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSJib2xkIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjM1Ni40NzM2IiB4PSI5Ljg3NCIgeT0iMjkwLjQyMTkiPlN5bnRheCBFcnJvcj8gKEFzc3VtZWQgZGlhZ3JhbSB0eXBlOiBjbGFzcyk8L3RleHQ+PD9wbGFudHVtbC1zcmMgQXlhaW9LYkxVRG15eU43Sl9pVkRVbnV0UnR1d2VQVUJBcEVsOUJLZUJKNHZMSzc5RUotdDgzeWxEUXo0SUk2bk1DNVBlUGZCRXh5V2pJWW45M0NfSnFGSklZNGVqUldxZnVJaGJlaWhFSVVuazFZYThJLXZBQkthckg2aW1INVRzMGFpeENKNXZRbldLd0VkMXJHMFBKWG45SUdhMDAwMD8+PC9nPjwvc3ZnPg==" class="uml" alt="uml diagram" title="" />

### 4.2 よく使うダイアグラムタイプ

- **クラス図**: パターン構造
- **シーケンス図**: 処理フロー
- **状態図**: 状態遷移
- **オブジェクト図**: 構造共有

---

## Phase 5: 品質チェックリスト

### 5.1 コード品質

- [ ] すべてのテストがパス
- [ ] 言語のイディオムに従っている
- [ ] 型安全性が確保されている
- [ ] エラーハンドリングが適切

### 5.2 ドキュメント品質

- [ ] Clojure 版の内容を網羅
- [ ] 言語固有の説明を追加
- [ ] コード例が正しく動作
- [ ] ダイアグラムが PlantUML

### 5.3 整合性

- [ ] mkdocs.yml が更新済み
- [ ] index.md に全章リンク
- [ ] ファイル命名規則に準拠

---

## Phase 6: コミット規約

### 6.1 コミットメッセージ形式
Add Chapter {N}: {Title} in {Language}

  • Implement {Component}.{ext} with {description}
  • {Feature1}
  • {Feature2}
  • {X} tests for all components
  • Part {N} total: {Y} tests
    ### 6.2 コミット粒度
    
    - 1章 = 1コミット(推奨)
    - 大きな章は分割可能:
      - コード実装
      - ドキュメント作成
      - mkdocs.yml 更新
    
    ---
    
    ## 付録 A: 言語別テンプレート
    
    ### TypeScript
    
    apps/typescript/part1/ ├── package.json ├── tsconfig.json ├── src/ │ └── chapter01.ts └── test/ └── chapter01.test.ts
    ```json
    // package.json
    {
      "name": "functional-design-part1",
      "scripts": {
        "test": "vitest run",
        "test:watch": "vitest"
      },
      "devDependencies": {
        "typescript": "^5.0.0",
        "vitest": "^1.0.0"
      }
    }
    

Rust

apps/rust/part1/
├── Cargo.toml
└── src/
    ├── lib.rs
    └── chapter01.rs
# Cargo.toml
[package]
name = "functional-design-part1"
version = "0.1.0"
edition = "2021"

[dev-dependencies]
# 必要に応じて追加

Go

apps/go/part1/
├── go.mod
├── chapter01.go
└── chapter01_test.go
// go.mod
module functional-design/part1

go 1.21

付録 B: 参考リソース

書籍

  • "Functional Design: Principles, Patterns, and Practices" - Robert C. Martin
  • 各言語の関数型プログラミング関連書籍

Clojure → 他言語

  • Clojure の -> マクロ → メソッドチェーン or パイプ演算子
  • Clojure の atom → 各言語の並行プリミティブ
  • Clojure の spec → 各言語のバリデーションライブラリ

テストライブラリ

言語 プロパティベーステスト
TypeScript fast-check
Rust proptest, quickcheck
Go gopter
Kotlin kotest-property
F# FsCheck

付録 C: よくある質問

Q: どの章から始めるべき?

A: 第1章から順番に進めることを推奨。基礎概念が後の章で使われるため。

Q: Clojure の機能が言語にない場合は?

A: 言語のイディオムで同等の機能を実現。完全な1対1対応は不要。

Q: テスト数の目安は?

A: Scala 実装を参考に: - Part 1-2: 各章 20-40 テスト - Part 3-5: 各章 30-50 テスト - Part 6-7: 各章 40-70 テスト

Q: ドキュメントの長さは?

A: Clojure 版と同程度。言語固有の説明で若干増減あり。


Simple made easy.