【Haskell学習|初心者向け】エラーを「例外」にしない!関数型プログラミングで学ぶ堅牢なエラー処理

なぜエラーを制御フローとして扱うのか?

プログラミングにおいて、エラー処理は避けて通れない課題です。多くの言語では「例外(try-catch)」が使われますが、これは突然プログラムの流れを中断させる「副作用」を伴うため、コードの予測可能性を下げてしまいます。関数型プログラミングでは、エラーを「特別な割り込み」ではなく、通常のプログラムの流れ(制御フロー)の一部として扱います。これを実践することで、コードが劇的に読みやすく、かつ安全になります。

基礎知識:エラーの「可視化」とは

例外を使うと、関数が正常に値を返すのか、それとも例外を投げるのか、呼び出し元からは見た目だけでは判断できません。一方、関数型プログラミングでは、エラーが発生する可能性を関数の戻り値に含めます。例えば「成功した値」と「失敗した理由」をひとつのデータ構造として扱う手法です。これにより、開発者は「エラーの可能性を考慮し忘れる」というミスを、コンパイラのチェックによって未然に防ぐことができるようになります。

実装:Result型によるエラー処理

今回は、エラーを値として扱う代表的な手法である「Result型」を使って、除算処理を実装してみましょう。例外を投げずに、成功か失敗かを型として表現します。

サンプルプログラム

以下のコードは、JavaScript(TypeScript風)でResult型をシミュレートした例です。

// 成功と失敗を表現するデータ構造
type Result = { type: ‘ok’, value: T } | { type: ‘err’, error: E };

// 0除算を安全に行う関数
function safeDivide(a: number, b: number): Result {
if (b === 0) {
// 例外を投げず、エラーを「値」として返す
return { type: ‘err’, error: ‘0で割ることはできません’ };
}
return { type: ‘ok’, value: a / b };
}

// 実行と結果のハンドリング
const result = safeDivide(10, 0);

// 通常の制御フロー(switch/if)で結果を分岐させる
switch (result.type) {
case ‘ok’:
console.log(‘計算成功:’, result.value);
break;
case ‘err’:
// エラー処理を明示的に記述するため、見落としが発生しない
console.error(‘エラー発生:’, result.error);
break;
}

応用・注意点:現場で役立つ考え方

この手法の最大のメリットは、「エラーを処理しなければコンパイルが通らない(または警告が出る)」という状態を作れることです。現場で陥りやすいバグの多くは、例外をキャッチし忘れたり、予期せぬ場所で例外が発生したりすることに起因します。

注意点として、すべての処理をResult型で包むとコードが冗長に感じられることもあるでしょう。その場合は、関数の合成(パイプライン処理)を活用して、エラー伝播を自動化するライブラリや手法を取り入れるのが近道です。まずは「エラーも大切な戻り値のひとつ」と考えることから始めてみてください。そうすれば、あなたの書くコードは驚くほど堅牢で、推論しやすいものへと進化するはずです。

コメント

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