最初のケースで何が起こるかを調べてみましょう。
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
アクションとして edであるサンクhead []
を作成します。このサンクは評価されないため、例外をスローしません。そのため、呼び出し全体(タイプは)で例外なしでサンクが生成されます。例外は、ghci が後で結果を出力しようとしたときにのみ発生します。試したらreturn
IO
catch (return $ head []) $ ...
IO String
String
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
>> return ()
代わりに、例外は出力されませんでした。
これが _" * Exception: Prelude.head: empty list_を取得する理由でもあります。GHCi は、 で始まる文字列の出力を開始し"
ます。次に、文字列を評価しようとしますが、その結果、例外が発生し、これが出力されます。 .
次のように(引数を WHNF に強制します) に置き換えてみてくださいreturn
。evaluate
catch (evaluate $ head []) $ \(e :: SomeException) -> return "good message"
catch
次に、例外をスローし、ハンドラーがそれをインターセプトできるようにする内部でサンクを強制的に評価します。
それ以外の場合
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
調べようとするとcatch
パーツ内で例外が発生するため、ハンドラーによってキャッチされます。print
head []
更新:お勧めのように、値を強制的に、できれば完全な通常の形式にすることをお勧めします。このようにして、怠惰なサンクであなたを待っている「驚き」がないことを確認します. とにかくこれは良いことです。たとえば、スレッドが評価されていないサンクを返し、それが実際には別の無防備なスレッドで評価される場合、見つけにくい問題が発生する可能性があります。
モジュールControl.Exception
にはすでに がありevaluate
、その 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