1. 導入:なぜエラーの伝え方が重要なのか
実務において、フォーム入力のバリデーションやAPIレスポンスの処理を行う際、「どこでエラーが起きたか」をユーザーにどう伝えるかは非常に重要です。多くの開発者が慣れ親しんでいるMaybe型(またはOptional)は、値の有無を表現するには適していますが、エラーが複数発生した場合に「最初の一つしか検知できない」という限界があります。本記事では、ユーザーに「一度にすべての修正箇所を提示する」ためのValidation型の設計思想と実装について解説します。
2. 基礎知識:MaybeとValidationの違い
Maybe型(Optional)は、計算の失敗を「Nothing」や「null」として表現します。これは「処理が成功したか否か」を判断するのには適していますが、失敗の理由(原因)を保持する能力が弱いです。
一方、Validation型は、成功した場合は値(Success)を、失敗した場合はエラーのリスト(Failure)を蓄積する構造を持っています。この違いにより、Maybeは「処理を即座に停止(Short-circuit)」させ、Validationは「全項目をチェックしてエラーを収集(Accumulation)」することが可能です。
3. 実装/解決策:Applicative Functorを活用したエラー蓄積
Validationを実装する鍵は、エラーを単に返すのではなく、リストとして連結することです。関数型プログラミングの文脈では、Applicative Functorを利用して、各バリデーションの結果を「失敗した場合はリストを合体させる」というロジックで結合します。これにより、独立した複数のバリデーション(名前、メールアドレス、年齢など)を同時に実行できます。
4. サンプルプログラム:TypeScriptによるValidationの実装例
以下は、エラーを蓄積するValidationパターンの簡易的な実装例です。
// 成功と失敗を表す型定義
type Validation<E, T> =
| { type: 'Success'; value: T }
| { type: 'Failure'; errors: E[] };
// 名前チェック
const validateName = (name: string): Validation<string, string> =>
name.length > 0 ? { type: 'Success', value: name } : { type: 'Failure', errors: ['名前を入力してください'] };
// 年齢チェック
const validateAge = (age: number): Validation<string, number> =>
age >= 18 ? { type: 'Success', value: age } : { type: 'Failure', errors: ['18歳以上である必要があります'] };
// 複数の結果を統合する関数
const validateUser = (name: string, age: number) => {
const nameResult = validateName(name);
const ageResult = validateAge(age);
// 両方成功した場合
if (nameResult.type === 'Success' && ageResult.type === 'Success') {
return { type: 'Success', value: { name: nameResult.value, age: ageResult.value } };
}
// エラーを収集して連結する
const errors = [
...(nameResult.type === 'Failure' ? nameResult.errors : []),
...(ageResult.type === 'Failure' ? ageResult.errors : [])
];
return { type: 'Failure', errors };
};
// 実行例: 両方のバリデーションに失敗する場合
const result = validateUser('', 15);
if (result.type === 'Failure') {
console.log('エラーが発生しました:', result.errors); // ['名前を入力してください', '18歳以上である必要があります']
}
5. 応用・注意点:現場での運用
Validation型を採用する際、注意すべき点は「依存関係の制御」です。すべてのチェックを同時に行う必要はなく、例えば「メールアドレスの形式が正しい場合のみ、データベースに存在確認を行う」といった依存がある場合は、Maybe型の性質(Short-circuit)と組み合わせる必要があります。
現場では、「独立した入力バリデーションはValidationでまとめて返し、ビジネスロジックの依存関係はMaybeやResult型で順次処理する」という使い分けを推奨します。これにより、ユーザーにとって親切なUIと、開発者にとって堅牢なバックエンド処理を両立させることができます。

コメント