有人可以解释以下ghci中行为与行之间的区别:
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
返回
"*** Exception: Prelude.head: empty list
但是
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
返回
"good message"
为什么第一个案例没有抓住异常?他们为什么不同?为什么第一个案例在异常消息之前加上双引号?
感谢。
答案 0 :(得分:25)
让我们来看看第一种情况会发生什么:
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
您创建了head []
作为return
操作的thunk IO
。这个thunk不会抛出任何异常,因为它没有被计算,所以整个调用catch (return $ head []) $ ...
(类型为IO String
)会产生String
thunk而没有异常。仅当ghci尝试之后打印结果时才会发生异常。如果你试过
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
>> return ()
相反,不会打印任何例外。
这也是你得到_“ * 异常的原因:Prelude.head:empty list_.GHCi开始打印字符串,以"
开头。然后它尝试评估字符串,这会导致异常,并打印出来。
尝试将return
替换为evaluate
(强制其参数为WHNF)
catch (evaluate $ head []) $ \(e :: SomeException) -> return "good message"
然后你将强制thunk在catch
内部进行评估,这将抛出异常并让处理程序拦截它。
在另一种情况下
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
当catch
尝试检查print
时,head []
部分内发生异常,因此它被处理程序捕获。
更新:正如您所说,一个好处是强制值,最好是完全正常的形式。通过这种方式,您可以确保在懒惰的thunk中没有“惊喜”等着你。无论如何这是一件好事,例如,如果你的线程返回一个未经评估的thunk并且它实际上是在另一个毫无疑问的线程中进行评估的话,你可能会遇到难以发现的问题。
模块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
,则可以按预期工作。
答案 1 :(得分:10)
return $ head []
将head []
包装在IO操作中(因为catch
具有IO
类型,否则它将是任何monad)并返回它。没有任何东西被捕获,因为没有错误。由于懒惰,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
答案 2 :(得分:6)
return
, GHCi works in the IO
monad和IO
并未强制论证。因此return $ head []
不会抛出任何异常,并且无法捕获未抛出的异常。 打印之后的结果会引发异常,但此异常不在catch
的范围内。
答案 3 :(得分:2)
类型IO a
包含两部分:
IO
表示。a
表示。 catch
函数只有在突破IO
结构时才会捕获异常。
在第一个示例中,您在值上调用print
。由于打印值需要对其执行I / O,因此值中引发的任何异常最终都会在结构本身中出现。所以catch
拦截了异常并且一切都很顺利。
另一方面,return
不检查其论点。事实上,monad法则保证你调用return
根本不会影响结构。所以你的价值只是直接通过。
因此,如果您的代码不受异常影响,那么错误消息来自何处?令人惊讶的是,答案在你的代码之外。 GHCi隐式尝试print
传递给它的每个表达式。但到那时,我们已经超出catch
的范围,因此您会看到错误消息。