Haskell で Control.Exception.catch を正しく使用するにはどうすればいいですか? 質問する

Haskell で Control.Exception.catch を正しく使用するにはどうすればいいですか? 質問する

誰か、ghci での次の 2 行の動作の違いを説明していただけますか。

catch (return $ head []) $ \(e :: SomeException) -> return "good message"

戻り値

"*** Exception: Prelude.head: empty list

しかし

catch (print $ head []) $ \(e :: SomeException) -> print "good message"

戻り値

"good message"

最初のケースでは例外がキャッチされないのはなぜですか? これらはなぜ異なるのですか? また、最初のケースでは例外メッセージの前に二重引用符が付けられているのはなぜですか?

ありがとう。

ベストアンサー1

最初のケースで何が起こるかを見てみましょう。

catch (return $ head []) $ \(e :: SomeException) -> return "good message"

アクションとして実行されるhead []サンクを作成します。このサンクは評価されないため例外をスローしません。そのため、呼び出し全体(型)は例外なしでサンクを生成します。例外は、ghciがその後結果を印刷しようとしたときにのみ発生します。returnIOcatch (return $ head []) $ ...IO StringString

catch (return $ head []) $ \(e :: SomeException) -> return "good message"
    >> return ()

代わりに、例外は印刷されませんでした。

これが、_ を取得する理由でもあります。"*例外: Prelude.head: 空のリスト_。GHCi は で始まる文字列を出力し始めます"。次に文字列を評価しようとしますが、その結果例外が発生し、これが出力されます。

を(引数をWHNFに強制する)returnに置き換えてみてください。evaluate

catch (evaluate $ head []) $ \(e :: SomeException) -> return "good message"

次に、内部でサンクを強制的に評価しcatch、例外をスローしてハンドラーがそれをインターセプトできるようにします。

他のケースでは

catch (print $ head []) $ \(e :: SomeException) -> print "good message"

検査しようとするcatchとパーツ内で例外が発生し、ハンドラーによってキャッチされます。printhead []


アップデート:おっしゃるとおり、値を強制的に、できれば完全な正規形にするのが良いでしょう。こうすることで、遅延サンクで「サプライズ」が起こらないようにすることができます。これはいずれにしても良いことです。たとえば、スレッドが評価されていないサンクを返し、それが別の予期しないスレッドで実際に評価された場合、見つけにくい問題が発生する可能性があります。

モジュールControl.Exceptionにはすでに がありevaluate、これにより thunk が WHNF に強制されます。これを簡単に拡張して、完全な NF に強制することができます。

import Control.DeepSeq
import Control.Seq
import Control.Exception
import Control.Monad

toNF :: (NFData a) => a -> IO a
toNF = evaluate . withStrategy rdeepseq

catchこれを使用して、特定のアクションをその NF に強制するの厳密なバリアントを作成できます。

strictCatch :: (NFData a, Exception e) => IO a -> (e -> IO a) -> IO a
strictCatch = catch . (toNF =<<)

この方法により、返された値が完全に評価されることが保証されるため、検査時に例外は発生しません。最初の例strictCatchで の代わりにを使用するとcatch、期待どおりに動作することを確認できます。

おすすめ記事