概要
- 型駆動設計 の本質を「 Parse, don’t validate」というスローガンで表現
- 部分関数を 全域関数 へ変換する手法の解説
- 型システム による安全性と情報伝達の強化
- バリデーションと パース の違いを明確化
- NonEmpty型 の活用例を通じて実践的な設計方法を紹介
型駆動設計の本質:「Parse, don’t validate」
- 型駆動設計 とは、静的型システムを活用して設計や実装を行う開発手法
- 最も端的に表現するスローガンが「 Parse, don’t validate」(パースせよ、バリデーションするな)
- このアプローチでは、 情報の消失を防ぎ、型で知識を伝搬 させることが重要
- バリデーションは条件を満たすか確認するだけだが、 パースは構造化されたデータを生成 し、型安全性を高める
- 型システムを活用することで、 実装ミスやバグの予防、保守性の向上 を実現
型システムが示す「実装可能性」
- 静的型付き言語(例: Haskell)では、型シグネチャが実装可能性の有無を明確に示す
- 例:
foo :: Integer -> Voidは実装不可能。Void型には値が存在しないため - 例:
head :: [a] -> aは一見単純だが、空リスト[]には対応できず、 部分関数 となる - コンパイラ警告により、 未定義ケース (例:空リスト)の存在を明示
部分関数を全域関数へ変換
- 部分関数(例:
head :: [a] -> a)は、すべての入力に対して定義されていない - 全域関数化の方法1: 戻り値を弱める (例:
head :: [a] -> Maybe a)- 空リスト時は
Nothingを返し、型安全性を確保 - しかし、呼び出し側で毎回
Nothingの考慮が必要となり、冗長かつバグの温床
- 空リスト時は
- 全域関数化の方法2: 引数型を強化 (例:
head :: NonEmpty a -> a)NonEmpty型を利用し、 空リスト不可能性を型で保証- チェックは一度だけ、 以降は安全に利用可能
NonEmpty型による設計の実践
NonEmpty a型は、必ず1つ以上の要素を持つリストを表現- 構造:
data NonEmpty a = a :| [a]
- 構造:
nonEmpty :: [a] -> Maybe (NonEmpty a)関数で、通常リストから変換- 例:設定ディレクトリの取得
- 入力値のチェックと
NonEmpty変換を 早期に一度だけ実施 - 以降の処理(例:
headでの利用)は 型による安全性 が保証される
- 入力値のチェックと
型で知識を伝搬する設計の利点
- 冗長なチェック不要、パフォーマンス劣化なし
- 入力チェックを省略した場合、 型不整合で即座にコンパイルエラー
head' :: [a] -> Maybe a = fmap head . nonEmptyのように、 従来型の挙動も簡単に再現可能- 逆は不可:情報を失った型からは、強化型を安全に生成できない
「パース」と「バリデーション」の違い
- バリデーション:条件を満たすか確認し、 情報は返さない (例:
validateNonEmpty :: [a] -> IO ()) - パース:条件を満たす場合、 構造化データ(型)を返す (例:
parseNonEmpty :: [a] -> IO (NonEmpty a)) - パースは、 入力の知識を型として呼び出し側に伝達 できる
- 型システムの恩恵を最大限に活かすには、「 バリデーションではなくパースを選択」する設計が重要
まとめ:「Parse, don’t validate」の実践
- 型駆動設計 は、情報を型で表現し、 安全性と保守性を高める手法
- パースは、 情報の伝搬と型安全性 を両立
- バリデーションのみでは、 知識が失われ型の恩恵を活かしきれない
- NonEmpty型 のような型を活用し、 型システムを証明道具として利用 する設計が推奨
次のトピックや観点が必要な場合は指示してください。