【Haskell学習|豆知識】代数的データ型(ADT)で実現する、堅牢でミスに強いデータ設計

導入

プログラミングにおいて、システムの状態やビジネスロジックが複雑化し、「予期せぬデータの組み合わせ」によってバグが発生した経験はありませんか?多くのプログラミング言語で使われるクラスやオブジェクトだけでは、データの「有効な状態」を保証するのが難しい場合があります。そこで強力な武器となるのが「代数的データ型(Algebraic Data Types: ADT)」です。ADTを使うことで、データが取りうる状態を型システムレベルで厳密に定義し、実行時の例外を未然に防ぐ堅牢なアプリケーションを構築できるようになります。

基礎知識

ADTとは、数学的な「和」と「積」の組み合わせによって新しいデータ型を構築する手法です。
積型(Product Type)は、複数の値を同時に持つ型のことです。例えば、ユーザー情報(名前、年齢、メールアドレス)をまとめたレコード型などがこれに当たります。
和型(Sum Type)は、複数の型のうち「いずれか一つ」を持つ型のことです。例えば、「成功」または「失敗」のどちらか一方の状態しか取らない処理結果などが典型例です。
これらを組み合わせることで、複雑な業務ドメインを正確に型で表現できます。

実装/解決策

ADTを実装する際は、まず「データが取りうる状態」を洗い出します。例えば、支払い処理の状態をモデル化する場合、「未処理」「成功(決済IDを持つ)」「失敗(エラーメッセージを持つ)」の3つに分類できます。これを和型として定義することで、コンパイラが「すべての場合(成功時と失敗時の両方の処理)」を考慮しているかを検証してくれるようになります。

サンプルプログラム

今回は関数型プログラミング言語の代表格であるHaskellの構文を例に、支払い状態を定義します。

// 支払い状態を表す代数的データ型
// 3つの状態のうち、必ずどれか1つになります
data PaymentStatus
= Pending // 未処理状態
| Success String // 成功状態(決済IDを保持)
| Failure String // 失敗状態(エラー内容を保持)

// 状態に基づいた処理を行う関数
processPayment :: PaymentStatus -> String
processPayment status = case status of
Pending -> “処理中です…”

// 成功した場合は決済IDを取り出して利用可能
Success txId -> “決済完了! ID: ” ++ txId

// 失敗した場合はエラー内容に応じた処理が可能
Failure msg -> “エラーが発生しました: ” ++ msg

// メイン処理のシミュレーション
main :: IO ()
main = do
let result = Success “TXN_12345”
putStrLn (processPayment result)

応用・注意点

ADTを設計する際のコツは、「不正な状態を表現不可能にする」ことです。例えば、`Success`なのに`txId`が空であるような状態を作らないよう、型の定義時に必要な情報だけを保持させるのがポイントです。
また、多くの言語(TypeScriptのDiscriminated UnionsやRustのEnumなど)でADTに近い機能が提供されています。現場では、if文やnullチェックを多用する前に「このデータは和型で表現できないか?」と考えるだけで、コードの可読性と安全性が劇的に向上します。ぜひ、複雑な条件分岐の整理からADTを取り入れてみてください。

コメント

タイトルとURLをコピーしました