如何使用CallStack引发异常?

Leo*_*ang 4 haskell exception

我有一个Exception type UnknownException,我想在抛出它时包括CallStack。

module Main where

import Control.Exception (Exception, throw)

newtype UnknownException = UnknownException
  { caller :: String
  } deriving (Show)

instance Exception UnknownException

main :: IO ()
main = willThrow

willThrow :: IO ()
willThrow = throw $ UnknownException "willThrow"
Run Code Online (Sandbox Code Playgroud)

我想要上面的示例打印这样的日志

example-exe: UnknownException {caller = "willThrow"}
CallStack (from HasCallStack):
  willThrow, called at app/Main.hs:16:13 in main:Main
  main, called at app/Main.hs:13:8 in main:Main
Run Code Online (Sandbox Code Playgroud)

但实际上打印:

example-exe: UnknownException {caller = "willThrow"}
Run Code Online (Sandbox Code Playgroud)

另外,在Haskell的异常中包括CallStack是一个好习惯吗?

luq*_*qui 5

是的,我认为这是一种好习惯。如果要进行概要分析,则只需使用

currentCallStack :: IO [String]
Run Code Online (Sandbox Code Playgroud)

来自GHC.Stack。请注意,它在其中,IO但是unsafePerformIO如果您使用纯代码,则抛出错误时,我认为这样做很好。因为所有底部在含义上都是相等的,所以实际上并没有任何违反纯度的行为。

但是,如果您希望不进行概要分析而直接进入调用堆栈(例如,您想将其包含在生产中的日志消息中),则必须做更多的事情。您必须HasCallStack在希望报告堆栈的所有位置都包含约束。所以

main :: IO ()
main = print f

f :: Int
f = g

g :: HasCallStack => Int
g = h

h :: HasCallStack => Int
h = error (show callStack)
Run Code Online (Sandbox Code Playgroud)

最多报告呼叫堆栈g,但将省略f。可悲的是

如果作用域中没有CallStack并且封闭定义具有显式类型签名,则GHC将为仅包含当前呼叫站点的单例CallStack解决HasCallStack约束。

这意味着它将忽略所有的调用者f,即使它们的调用者HasCallStack只是因为f没有这样的约束而已。我觉得这很麻烦。这是一个相当新的功能,因此我希望GHC团队可以更好地记住这一点。