Eya*_*yal 21 haskell exception
有人可以解释以下ghci中行为与行之间的区别:
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
Run Code Online (Sandbox Code Playgroud)
回报
"*** Exception: Prelude.head: empty list
Run Code Online (Sandbox Code Playgroud)
但
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
Run Code Online (Sandbox Code Playgroud)
回报
"good message"
Run Code Online (Sandbox Code Playgroud)
为什么不是第一个抓住异常的案例?他们为什么不同?为什么第一个案例在异常消息之前加上双引号?
谢谢.
Pet*_*lák 25
让我们来看看第一种情况会发生什么:
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
Run Code Online (Sandbox Code Playgroud)
您创建的thunk head []被return编为一IO行动.这个thunk不会抛出任何异常,因为它没有被计算,所以整个调用catch (return $ head []) $ ...(类型IO String)会产生String没有异常的thunk.仅当ghci尝试之后打印结果时才会发生异常.如果你试过
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
>> return ()
Run Code Online (Sandbox Code Playgroud)
相反,不会打印任何例外.
这也是为什么你得到_" * Exception:Prelude.head:empty list_.GHCi开始打印字符串,它开始于".然后它尝试评估字符串,这导致异常,这打印出来.
尝试替换return为evaluate(强制其参数为WHNF)为
catch (evaluate $ head []) $ \(e :: SomeException) -> return "good message"
Run Code Online (Sandbox Code Playgroud)
那么你将强制thunk进行内部评估,catch这将抛出异常并让处理程序拦截它.
在另一种情况下
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
Run Code Online (Sandbox Code Playgroud)
尝试检查catch时,部件内部发生异常,因此它被处理程序捕获.printhead []
更新:正如你的建议,一个好处是强制值,最好是完全正常的形式.通过这种方式,您可以确保在懒惰的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
Run Code Online (Sandbox Code Playgroud)
使用这个,我们可以创建一个严格的变体catch,强制给定的动作到它的NF:
strictCatch :: (NFData a, Exception e) => IO a -> (e -> IO a) -> IO a
strictCatch = catch . (toNF =<<)
Run Code Online (Sandbox Code Playgroud)
这样,我们确信已完全评估返回的值,因此在检查时我们不会得到任何异常.您可以验证,如果您使用strictCatch而不是catch在第一个示例中,它可以按预期工作.
fir*_*dle 10
return $ head []
Run Code Online (Sandbox Code Playgroud)
包装head []在IO动作中(因为catch有一个IO类型,否则它将是任何monad)并返回它.没有任何东西被捕获,因为没有错误.head []由于懒惰,本身没有被评估,但只返回.因此,return只添加一层包装,整个catch表达式的结果head []非常有效,未经评估.只有当GHCi或您的程序实际上尝试在稍后使用该值时,才会对其进行评估并抛出空列表错误 - 但是在不同的点上.
print $ head []
Run Code Online (Sandbox Code Playgroud)
另一方面,立即评估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
Run Code Online (Sandbox Code Playgroud)
为避免这种情况,您可以强制使用该值:
Prelude> let x = head [] in x `seq` return x
*** Exception: Prelude.head: empty list
Run Code Online (Sandbox Code Playgroud)