24

誰かが次の行のghciでの動作の違いを説明できますか:

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

戻り値

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

しかし

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

戻り値

"good message"

最初のケースで例外がキャッチされないのはなぜですか? なぜ違うのですか?そして、なぜ最初のケースでは、例外メッセージの前に二重引用符を付けているのでしょうか?

ありがとう。

4

4 に答える 4

27

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

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

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

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

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

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

次のように(引数を WHNF に強制します) に置き換えてみてくださいreturnevaluate

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

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

それ以外の場合

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

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


更新:お勧めのように、値を強制的に、できれば完全な通常の形式にすることをお勧めします。このようにして、怠惰なサンクであなたを待っている「驚き」がないことを確認します. とにかくこれは良いことです。たとえば、スレッドが評価されていないサンクを返し、それが実際には別の無防備なスレッドで評価される場合、見つけにくい問題が発生する可能性があります。

モジュール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

于 2013-08-05T08:32:24.097 に答える
10
return $ head []

head []IO アクションでラップし (タイプcatchがあるためIO、それ以外の場合は任意のモナドになります)、それを返します。エラーがないので引っかかることはありません。head []遅延のおかげで、それ自体はその時点では評価されず、返されるだけです。したがって、returnラッピングのレイヤーを追加するだけで、catch 式全体の結果はhead []、非常に有効で、評価されていません。GHCiまたはあなたのプログラムが後でその値を実際に使用しようとした場合にのみ評価され、空のリストエラーがスローされます - ただし、別の時点で。

print $ head []

一方、 はすぐに を評価しhead []、後でキャッチされるエラーを生成します。

GHCi の違いも確認できます。

Prelude> :t head []
head [] :: a

Prelude> :t return $ head []
return $ head [] :: Monad m => m a

Prelude> :t print $ head []
print $ head [] :: IO ()

Prelude> return $ head [] -- no error here!

Prelude> print $ head []
*** Exception: Prelude.head: empty list   

それを避けるために、おそらく値を強制することができます:

Prelude> let x = head [] in x `seq` return x
*** Exception: Prelude.head: empty list
于 2013-08-05T08:34:55.737 に答える
6

GHCi はIOモナド内で機能し、returnforIOはその引数を強制しません。したがってreturn $ head []、例外はスローされず、スローされない例外はキャッチできません。後で結果を出力すると例外がスローされますが、この例外はcatchもはやスコープ外です。

于 2013-08-05T08:33:43.227 に答える