Haskellでエラーを報告する最もクリーンな方法


22

私はHaskellの学習に取り組んでおり、私が書いた関数のエラーに対処する3つの異なる方法に出くわしました。

  1. 単にerror "Some error message."例外をスローするように書くことができます。
  2. 私は自分の関数を返すMaybe SomeTypeことができます。私は返すものを返すことができる場合とできない場合があります。
  3. 関数を返すEither String SomeTypeことができます。エラーメッセージまたは最初に返されるように求められたものを返すことができます。

私の質問は、どのエラー処理方法を使用すればよいのか、そしてその理由は何ですか?コンテキストに応じて異なる方法を使用する必要がありますか?

私の現在の理解は:

  • 純粋に機能的なコードの例外に対処するのは「困難」であり、Haskellでは、できる限り純粋に機能的なものに保ちたいと考えています。
  • Maybe SomeType関数が失敗または成功する場合(つまり、失敗する可能性のあるさまざまな方法がない場合)は、返すことが適切です。
  • Either String SomeType関数がさまざまな方法のいずれかで失敗する可能性がある場合、返すことは適切なことです。

回答:


32

さて、Haskellのエラー処理の最初のルール:を使用しないでくださいerror

それはあらゆる点でひどいです。それは純粋に歴史の行為として存在し、プレリュードがそれを使用するという事実はひどいものです。使用しないでください。

あなたがそれを使用できる唯一の考えられる時間は、何かが非常に内部的にひどいとき、何かが現実のまさしくそのファブリックで間違っていなければならないときです。

質問はMaybevsになりEitherます。Maybehead、値を返す場合と返さない場合がありますが、失敗する理由は1つだけです。Nothing「壊れた、そして、あなたはすでにその理由を知っている」のようなことを言っています。一部の機能を示していると言う人もいます。

エラー処理の最も堅牢な形式はEither、エラーADTです。

たとえば、私の趣味のコンパイラの1つには、次のようなものがあります。

data CompilerError = ParserError ParserError
                   | TCError TCError
                   ...
                   | ImpossibleError String

data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
             | MissingDefinition Name
             | InfiniteType Ty
             ...

type ErrorM m = ExceptT CompilerError m -- from MTL

ここで、エラータイプを多数定義し、それらをネストして、1つの素晴らしいトップレベルエラーを作成します。これは、コンパイルのどの段階からのエラーでもImpossibleError、コンパイラのバグを意味するでも構いません。

これらのエラータイプはそれぞれ、きれいな印刷やその他の分析のために、できるだけ多くの情報を保持しようとします。さらに重要なのは、文字列を持たないことで、型チェッカーを介して不正な型のプログラムを実行すると、実際に統合エラーが生成されることをテストできることです!何かがになるとString、それは永遠に消え、そこに含まれる情報はすべてコンパイラー/テストEither Stringにとって不透明であるため、どちらも素晴らしいものではありません。

最後にExceptT、この型をMTLからの新しいモナド変換子にパックします。これは基本的EitherTに、純粋で快適な方法でエラーをスローおよびキャッチするための機能の素晴らしいバッチが付属しています。

最後に、Haskellには、例外をキャッチすることを除いて、他の言語と同様に例外の処理をサポートするメカニズムがあることに言及する価値がありますIOIOすべてが潜在的に失敗する可能性のある重いアプリケーションにこれらを使用することを好む人もいますが、あまり頻繁ではないので、それについて考えるのが好きではありません。これらの不純な例外を使用するか、それとも単にExceptT Error IO好みの問題です。個人的にExceptTは、失敗の可能性を思い出すのが好きだから選びます。


要約すると、

  • Maybe -私は1つの明らかな方法で失敗することができます
  • Either CustomType -私は失敗することができます、そして何が起こったのかを教えます
  • IO+例外-私は時々失敗します。ドキュメントをチェックして、実行時にスローするものを確認します
  • error -私もあなたが嫌い​​です

この答えは私にとって非常に明確です。私は組み込み関数がのような、headまたはlast使用しているように見えるという事実に基づいて自分自身を推測していたerrorので、それが実際に物事を行うのに良い方法であり、何かが欠けているだけだと思っていました。これはその質問に答えます。:)
CmdrMoozy 14

5
@CmdrMoozy Glad to help :) preludeにそのような悪い習慣があるのは本当に残念です。これがレガシーの方法です:/
ダニエル・グラッツァー14

3
1あなたの概要は素晴らしいです
recursion.ninjaは

2

失敗する可能性のある方法に基づいて2と3を分離しません。考えられる方法が1つしかないと思うとすぐに、別の方法が顔を見せます。代わりに、メトリックは「呼び出し側何かが失敗した理由を気にしますか?実際にそれについて何かできるでしょうか?」である必要があります。

それを超えて、Either String SomeTypeエラー状態を引き起こす可能性があることはすぐにはわかりません。よりわかりやすい名前の条件を持つ単純な代数データ型を作成します。

どちらを使用するかは、直面している問題の性質と、使用しているソフトウェアパッケージのイディオムによって異なります。私は#1を避ける傾向がありますが。


実際、Eitherエラーに使用することはHaskellで非常によく知られているパターンです。
ラッフルウィンド14

1
@rufflewind- Either不明確な部分ではStringなく、実際の文字列ではなくエラーとして使用します。
テラスティン14

ああ、私はあなたの意図を誤解しています。
ラッフルウィンド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.