誰か、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がその後結果を印刷しようとしたときにのみ発生します。return
IO
catch (return $ head []) $ ...
IO String
String
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
とパーツ内で例外が発生し、ハンドラーによってキャッチされます。print
head []
アップデート:おっしゃるとおり、値を強制的に、できれば完全な正規形にするのが良いでしょう。こうすることで、遅延サンクで「サプライズ」が起こらないようにすることができます。これはいずれにしても良いことです。たとえば、スレッドが評価されていないサンクを返し、それが別の予期しないスレッドで実際に評価された場合、見つけにくい問題が発生する可能性があります。
モジュール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
、期待どおりに動作することを確認できます。