以下是我们拥有的一个非常普通的模式的简化,在这里您可以使用一些重试组合器来包装IO操作。我想要一些堆栈跟踪,所以我添加了HasCallStack
约束,但是生成的堆栈跟踪并不十分令人满意:
import Control.Monad (forM_)
import GHC.Stack
httpCall :: HasCallStack => IO ()
httpCall = do
putStrLn $ prettyCallStack callStack
print "http resolved"
retry :: HasCallStack => IO () -> IO ()
retry op =
forM_ [1] $ \i ->
op
main :: IO ()
main = retry httpCall
stacktrace:
CallStack (from HasCallStack):
httpCall, called at main.hs:16:14 in main:Main
"http resolved"
我假设在HasCallStack
中解析了main
约束以适合retry
的参数类型,所以我将约束添加到参数类型:
{-# LANGUAGE RankNTypes #-}
import Control.Monad (forM_)
import GHC.Stack
httpCall :: HasCallStack => IO ()
httpCall = do
putStrLn $ prettyCallStack callStack
print "http resolved"
retry :: HasCallStack => (HasCallStack => IO()) -> IO () -- NOTICE the constraint in the argument
retry op =
forM_ [1] $ \i ->
op
main :: IO ()
main = retry httpCall
现在stacktrace还有2个条目,它们都很令人惊讶:
CallStack (from HasCallStack):
httpCall, called at main.hs:17:14 in main:Main
op, called at main.hs:14:5 in main:Main
retry, called at main.hs:17:8 in main:Main
"http resolved"
httpCall
报告它是从main
调用的(第17行)op
报告正确的行,但是很意外地在堆栈跟踪中看到它。我期望达到以下目标:
CallStack (from HasCallStack):
httpCall, called at main.hs:14:5 in main:Main
retry, called at main.hs:17:8 in main:Main
"http resolved"
对于问题1和2,请注意,HasCallStack
调用栈在本质上是“词法”。
具体来说,关于“问题1”,调用堆栈中的调用位置(函数名称和行号)信息是基于语法在程序中出现的位置,而不是实际上强制调用的代码行被制造。因此,显示从第17行而不是从第14行调用httpCall
的原因是因为文字程序文本“ httpCall
”出现在第17行。类似地,对于“问题2”,第14行调用的事物在词法上出现为op
,而不是httpCall
,因此它就出现在调用堆栈中。
关于程序的行为:
[在您的第一个程序中,HasCallStack
和retry
中的httpCall
的main
约束通过将适当的调用条目(带有词法调用站点信息)推送到空调用堆栈中来解决(因为main
不提供HasCallStack
实例本身)。如果我们使调用堆栈参数明确,则代码将类似于:
httpCall :: CallStack -> IO ()
httpCall callStack = do
putStrLn $ prettyCallStack callStack
print "http resolved"
retry :: CallStack -> IO () -> IO ()
retry callStack op =
forM_ [1] $ \i ->
op
main :: IO ()
main = retry (push1 emptyCallStack) (httpCall (push2 emptyCallStack))
where push1 = pushCallStack ("retry", SrcLoc "" "" "" 15 7 15 11)
push2 = pushCallStack ("httpCall", SrcLoc "" "" "" 15 13 15 20)
请注意,callStack
的retry
自变量实际上记录了retry
调用(push1
)。评估httpCall
时,将传递push2 emptyCallStack
,它会为httpCall
中的main
调用生成单个条目。
我认为您所假设的一切或多或少都发生了(即HasCallStack
约束在main中得到解析以适合retry
的参数类型)。
在您的第二个程序中,可能发生了一些有趣的事情,但可能没有充分记录。 retry
调用的处理方式与以前相同,但是httpCall
-因为其键入为HasCallStack => IO ()
而不是IO ()
-产生的代码期望实例在范围内,而不是解析实例立即。使用显式的调用堆栈参数,为retry
和main
生成的代码现在看起来类似于:
retry :: CallStack -> (CallStack -> IO ()) -> IO ()
retry callStack op =
forM_ [1] $ \i ->
op (push3 callStack)
where push3 = pushCallStack ("op", SrcLoc "" "" "" 14 4 14 6)
main :: IO ()
main = retry (push1 emptyCallStack) (\callStack -> httpCall (push2 callStack))
where push1 = pushCallStack ("retry", SrcLoc "" "" "" 17 7 17 11)
push2 = pushCallStack ("httpCall", SrcLoc "" "" "" 17 13 17 20)
结果是httpCall
实际上以push2 $ push3 $ push1 emptyCallStack
作为其参数,因此您依次看到httpCall
,op
和retry
的三个条目。