web-dev-qa-db-ja.com

Haskellでの例外処理

3つのHaskell関数の使用法を理解するのに助けが必要

  • 試す(Control.Exception.try :: Exception e => IO a -> IO (Either e a)
  • キャッチ(Control.Exception.catch :: Exception e => IO a -> (e -> IO a) -> IO a
  • ハンドル(Control.Exception.handle :: Exception e => (e -> IO a) -> IO a -> IO a

いくつかのことを知る必要があります。

  1. どの機能をいつ使用しますか?
  2. この関数を簡単な例で使用するにはどうすればよいですか?
  3. Catchとhandleの違いはどこですか?それらは、順序が異なるだけでほぼ同じ署名を持っています。

トライアルを書き留めて、あなたが私を助けてくれることを願っています:

試用

私のような例があります:

x = 5 `div` 0
test = try (print x) :: IO (Either SomeException ())

2つの質問があります。

  1. カスタムエラー出力を設定するにはどうすればよいですか?

  2. すべてのエラーをSomeExceptionに設定するにはどうすればよいですか。:: IO (Either SomeException())

キャッチ/試行

カスタムエラー出力の短い例を教えてください。

72
develhevel

どの機能をいつ使用しますか?

Control.Exceptionドキュメントからの推奨事項は次のとおりです。

  • 例外が発生した場合に何らかのクリーンアップを行いたい場合は、finallybracket、またはonExceptionを使用します。
  • 例外の後に回復し、他のことを行うには、tryファミリーのいずれかを使用するのが最善の選択です。
  • ...非同期例外から回復する場合を除き、その場合はcatchまたはcatchJustを使用します。

try ::例外e => IO a-> IO(e a a)

tryIOアクションを実行してEitherを返します。計算が成功した場合、結果はRightコンストラクターにラップされて提供されます。 (間違っているのではなく、正しく考えてください)。アクションが指定されたタイプの例外をスローした場合Leftコンストラクターで返されます。例外が適切なタイプのnotであった場合、スタックを伝播し続けます。タイプとしてSomeExceptionを指定すると、すべての例外をキャッチしますが、これは良い考えかもしれません。

純粋な計算から例外をキャッチしたい場合は、evaluateを使用してtry内で評価を強制する必要があることに注意してください。

_main = do
    result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
    case result of
        Left ex  -> putStrLn $ "Caught exception: " ++ show ex
        Right val -> putStrLn $ "The answer was: " ++ show val
_

catch ::例外e => IO a->(e-> IO a)-> IO a

catchtryに似ています。最初に、指定されたIOアクションを実行しようとしますが、例外がスローされると、ハンドラーに例外が与えられ、代替の回答が得られます。

_main = catch (print $ 5 `div` 0) handler
  where
    handler :: SomeException -> IO ()
    handler ex = putStrLn $ "Caught exception: " ++ show ex
_

ただし、1つの重要な違いがあります。 catchを使用する場合、非同期例外(つまり、throwToを介して別のスレッドからスローされる)によってハンドラーを中断することはできません。非同期例外を発生させようとすると、ハンドラーの実行が完了するまでブロックされます。

Preludeには別のcatchがあるため、import Prelude hiding (catch)を実行することもできます。

ハンドル::例外e =>(e-> IO a)-> IO a-> IO a

handleは単純にcatchであり、引数の順序は逆です。どちらを使用するかは、コードを読みやすくするもの、または部分的なアプリケーションを使用する場合により適したものに依存します。それ以外は同じです。

tryJust、catchJust、およびhandleJust

trycatch、およびhandleは、指定/推論された型のall例外をキャッチすることに注意してください。 tryJustとそのフレンドを使用すると、具体的に処理する例外を除外するセレクター関数を指定できます。たとえば、すべての算術エラーはArithExceptionタイプです。 DivideByZeroだけをキャッチしたい場合は、次のことができます。

_main = do
    result <- tryJust selectDivByZero (evaluate $ 5 `div` 0)
    case result of
        Left what -> putStrLn $ "Division by " ++ what
        Right val -> putStrLn $ "The answer was: " ++ show val
  where
    selectDivByZero :: ArithException -> Maybe String
    selectDivByZero DivideByZero = Just "zero"
    selectDivByZero _ = Nothing
_

純度に関する注意

このタイプの例外処理は、不純なコード(つまり、IOモナド)でのみ発生することに注意してください。純粋なコードでエラーを処理する必要がある場合は、代わりにMaybeまたはEither(または他の代数データ型)を使用して値を返すことを検討する必要があります。これはより明示的であり、どこで何が起こるかを常に知っているので、これはしばしば好ましいです。 _Control.Monad.Error_のようなモナドを使用すると、このタイプのエラー処理が簡単になります。


こちらもご覧ください:

124
hammar

Edward Z. Yangには、haskellの例外処理に関する記事があります: Haskellでエラーを報告する8つの方法の再検討

5
haroldcarr

Re:質問3:キャッチとハンドルは samehoogle で発見)です。どちらを使用するかの選択は、通常、各引数の長さに依存します。アクションが短い場合は、catchを使用し、逆も同様です。ドキュメントからの単純なハンドルの例:

do handle (\NonTermination -> exitWith (ExitFailure 1)) $ ...

また、ハンドル関数をカリー化してカスタムハンドラーを作成することもできます。 (ドキュメントから改編):

let handler = handle (\NonTermination -> exitWith (ExitFailure 1))

カスタムエラーメッセージ:

do       
    let result = 5 `div` 0
    let handler = (\_ -> print "Error") :: IOException -> IO ()
    catch (print result) handler
1
Boris

私もあなたを悩ませる1つのこと(2番目の質問)が:: IO (Either SomeException ())の記述であることがわかり、それも私を悩ませます。

これからいくつかのコードを変更しました:

let x = 5 `div` 0
result <- try (print x) :: IO (Either SomeException ())
case result of
    Left _ -> putStrLn "Error"
    Right () -> putStrLn "OK"

これに:

let x = 5 `div` 0
result <- try (print x)
case result of
    Left (_ :: SomeException) -> putStrLn "Error"
    Right () -> putStrLn "OK"

これを行うには、ScopedTypeVariables GHC拡張機能を使用する必要がありますが、審美的には価値があると思います。

1