| name | exception |
| description | Do not throw exceptions from pure code with error or throw, give exceptions structured types, and never silently swallow errors in IO. Use when writing or reviewing Haskell error handling, exceptions, or IO code. |
| user-invocable | false |
例外処理の原則
error関数の禁止
純粋関数空間の中で例外を投げられるerror関数の使用は原則として禁止です。
haskell-tasuke:partial-function
でも触れましたが、
純粋関数空間で例外を投げてしまうと、
呼び出し側で捕捉するのが困難だからです。
また例外に入っているのがStringという非構造化されたデータであると、
デバッグが難しくなります。
throw(導入されている場合は非純粋であることを明示したimpureThrow)の方が、
例外に型がついているという点ではまだマシですが、
こちらも純粋関数空間で例外を投げているのは同じなのでやはり推奨されません。
呼び出し側にMonadThrowやEitherなどを使ってエラー情報を伝播させてください。
例外
論理的にどう考えても発生しないはずの場所なら許容されます。
そんなおかしなことが起きているなら、
アプリケーション自体を終了させるのが適切なレベルの話です。
それでもなるべく型を作って例外を投げるのが望ましいです。
undefinedはPRを出すときにはクリーンアップしましょう
Haskell標準で用意されているundefinedは、
開発中に仮の実装として使うのは便利です。
Rustのtodo!と同じように使えます。
例えばテストファーストで開発をする時に、
とりあえずコンパイルを通すだけの関数シンボルをundefinedで埋めて、
テストコードを書いてから実装を進めるといった使い方ができます。
しかしundefinedは結局は純粋空間でも例外を投げる関数なので、
PRを出すときにはundefinedをクリーンアップしてください。
残っていると警告が出るはずなので見落としはあまりないとは思いますが。
例外は型をつけよう
throwStringのような関数を使うより、
ちゃんと例外に型をつけてthrowMなどで型がついた例外を使いましょう。
例外の状況を伝えるデータ型は、
Textなどの文字列型を使うのではなく、
なるべく構造的なデータ型をフィールドとして持ってください。
ただしcycle importが発生する場合は、
呼び出し側でTextに変換するのもやむを得ないでしょう。
エラーを握り潰すのは禁止
IOの文脈などで例外が生じた場合に、
単に握りつぶして何もしない行為は禁止です。
IOは文脈的に既に例外が発生する可能性があることを示しているので、
例外が上位に伝播することは許容されています。
なので握りつぶすぐらいなら、
例外をそのまま上位に任せてしまう方が概ね適切です。
例外が生じた時に、
そこで例外を処理するのが完全に適切なら、
問題の程度に応じたログを出して処理してください。
単にデバッグを楽にするだけで解決は出来ない場合は例外を再送出してください。
例外だけではなくEitherのLeftなども適切に処理してください。
Leftに対してデフォルト値やフォールバック値を使ってください。
想定外で対処不能ならMonadThrowの文脈に載せて上位に任せてください。
IOが既にコンテキストにある状態でMaybeやEitherで包むのは微妙
IOは例外が発生する可能性がある文脈を十分に表現しているモナドなので、
その中でMaybeやEitherを使って例外的な状況を示すのは二重にネストしていて混乱を招きます。
素直に例外を投げてしまうのが良いでしょう。
基底モナドなどでIO的な操作をしているがIOそのものではないモナドの場合は、
MonadThrowやMonadIOの型クラスが役に立ちます。
例外
データベースやネットワークと通信してデータを取得するような操作は、
存在しないというケースが頻繁に発生する正常系として扱われます。
その場合はIO (Maybe a)のようなシグネチャを使うことは分かり易く適切です。