第16章: 給与計算システム — 6言語統合ガイド¶
1. はじめに¶
給与計算システムは、ドメインモデリングと多態的ディスパッチを実践的に学ぶためのケーススタディです。従業員の種類(時給制・月給制・歩合制)に応じた給与計算ロジックを、関数型パラダイムでどう設計するかを比較します。
2. 共通の本質¶
ドメインモデル¶
すべての言語で共通のドメイン概念:
- 従業員分類: 時給制(Hourly)、月給制(Salaried)、歩合制(Commissioned)
- 支払いスケジュール: 週次、月次、隔週
- 支払い方法: 銀行振込、小切手、手渡し
- 計算ルール: 残業(40 時間超)、歩合率、固定給
計算ロジック¶
時給制: 通常時間 × 時給 + 残業時間 × 時給 × 1.5
月給制: 固定月給
歩合制: 基本給 + 売上 × 歩合率
3. 言語別実装比較¶
3.1 従業員分類の型表現¶
| 言語 | 型表現 | 多態性の方法 |
|---|---|---|
| Clojure | マップ + キーワード | defmulti でディスパッチ |
| Scala | sealed trait + case class |
パターンマッチング |
| Elixir | 構造体 + タプルタグ | パターンマッチング |
| F# | 判別共用体 | パターンマッチング |
| Haskell | 代数的データ型 | case 式 |
| Rust | enum |
match 式 |
Clojure: マップ + マルチメソッド
(defmulti calculate-pay :pay-type)
(defmethod calculate-pay :hourly [{:keys [hours hourly-rate]}]
(let [regular (min hours 40)
overtime (max 0 (- hours 40))]
(+ (* regular hourly-rate)
(* overtime hourly-rate 1.5))))
(defmethod calculate-pay :salaried [{:keys [monthly-salary]}]
monthly-salary)
(defmethod calculate-pay :commissioned [{:keys [base-salary sales commission-rate]}]
(+ base-salary (* sales commission-rate)))
Scala: sealed trait + パターンマッチ
sealed trait PayClassification
case class Hourly(hourlyRate: Double) extends PayClassification
case class Salaried(monthlySalary: Double) extends PayClassification
case class Commissioned(baseSalary: Double, commissionRate: Double) extends PayClassification
def calculatePay(classification: PayClassification, hours: Double, sales: Double): Double =
classification match
case Hourly(rate) =>
val regular = math.min(hours, 40)
val overtime = math.max(0, hours - 40)
regular * rate + overtime * rate * 1.5
case Salaried(salary) => salary
case Commissioned(base, rate) => base + sales * rate
Haskell: ADT + case 式
data PayClassification
= Hourly Double
| Salaried Double
| Commissioned Double Double
calculatePay :: PayClassification -> Double -> Double -> Double
calculatePay classification hours sales = case classification of
Hourly rate ->
let regular = min hours 40
overtime = max 0 (hours - 40)
in regular * rate + overtime * rate * 1.5
Salaried salary -> salary
Commissioned base rate -> base + sales * rate
Rust: enum + match
pub enum PayClassification {
Hourly { hourly_rate: f64 },
Salaried { monthly_salary: f64 },
Commissioned { base_salary: f64, commission_rate: f64 },
}
impl PayClassification {
pub fn calculate(&self, hours: f64, sales: f64) -> f64 {
match self {
Self::Hourly { hourly_rate } => {
let regular = hours.min(40.0);
let overtime = (hours - 40.0).max(0.0);
regular * hourly_rate + overtime * hourly_rate * 1.5
}
Self::Salaried { monthly_salary } => *monthly_salary,
Self::Commissioned { base_salary, commission_rate } => {
base_salary + sales * commission_rate
}
}
}
}
3.2 データモデルの設計哲学¶
| 言語 | 設計哲学 | データ定義 |
|---|---|---|
| Clojure | データ駆動 | マップリテラル、スキーマは Spec で後付け |
| Scala | 型駆動 | case class 階層、コンパイル時チェック |
| Elixir | データ駆動 | 構造体、タプルタグで分類 |
| F# | 型駆動 | 判別共用体、コンパイル時網羅性チェック |
| Haskell | 型駆動 | ADT、最も厳密な型チェック |
| Rust | 型駆動 | enum、所有権 + 型安全性 |
動的型付け言語(Clojure, Elixir)はデータ構造が柔軟で、新しいフィールドの追加が容易です。静的型付け言語(Scala, F#, Haskell, Rust)はコンパイル時にドメインルールの整合性を保証します。
3.3 拡張性の違い¶
新しい従業員分類(例: 日給制)を追加する場合:
| 言語 | 変更箇所 | 安全性 |
|---|---|---|
| Clojure | defmethod を 1 つ追加 |
テストで確認 |
| Scala | case class 追加 + match に追加 | コンパイルエラーで検出 |
| Elixir | 関数クローズを追加 | テストで確認 |
| F# | 判別共用体に追加 + match に追加 | コンパイルエラーで検出 |
| Haskell | ADT に追加 + case に追加 | コンパイルエラーで検出 |
| Rust | enum に追加 + match に追加 | コンパイルエラーで検出 |
4. 比較分析¶
ドメインモデリングのトレードオフ¶
| 基準 | 動的型付け(Clojure, Elixir) | 静的型付け(Scala, F#, Haskell, Rust) |
|---|---|---|
| 柔軟性 | 高い(スキーマレス) | 型定義が必要 |
| 安全性 | テストに依存 | コンパイル時チェック |
| リファクタリング | リスクあり | コンパイラが支援 |
| プロトタイピング | 高速 | 型設計が必要 |
5. まとめ¶
給与計算システムは、関数型言語でのドメインモデリングの典型例です:
- 多態的ディスパッチ: 従業員分類ごとの計算を言語の多態性で表現
- データと処理の分離: データ定義と計算ロジックを明確に分離
- 拡張性: 新しい分類の追加が既存コードへの影響を最小化