AFAIK,有两种方法可以获取调用堆栈以在Haskell中进行调试:

  • 在代码
  • 中添加HasCallStack约束
  • 使用ghc -prof -fprof-auto-top编译代码

  • 我的测试代码:
    import GHC.Stack
    
    -- | a wrapper function to make "last" from base traceable
    last' :: HasCallStack => [a] -> a
    last' xs = case xs of [] -> error "abuse last"; _ -> last xs
    
    -- | a untraceable partial function
    foo :: [Int] -> Int
    foo xs = last' xs + 1
    
    -- | a untraceable partial function
    -- , which looks like traceable, but it's call stack is cut off by "foo"
    bar :: HasCallStack => [Int] -> Int
    bar xs = foo xs
    
    -- | an empty list
    -- , which is also an improper input of "last'"
    xs :: [Int]
    xs = []
    
    -- | the program is expected to print a call stack telling how "bar" is called
    -- , but we can only know that "foo" called "last'" improperly from the printed call stack
    main :: IO ()
    main = print $ bar xs
    

    以下是我通过this测试代码从以上两种方式获得的调用堆栈:
    $ ghc -prof -fprof-auto call-stack-cut-off.hs
    $ ./call-stack-cut-off
    call-stack-cut-off: abuse last
    CallStack (from HasCallStack):
      error, called at call-stack-cut-off.hs:5:29 in main:Main
      last', called at call-stack-cut-off.hs:9:10 in main:Main
    CallStack (from -prof):
      Main.last' (call-stack-cut-off.hs:5:1-60)
      Main.foo (call-stack-cut-off.hs:9:1-21)
      Main.bar (call-stack-cut-off.hs:14:1-15)
      Main.main (call-stack-cut-off.hs:24:1-21)
      Main.CAF (<entire-module>)
    

    IMO,来自-prof的调用堆栈已经足够好,并且更易于使用。所以我想知道为什么还添加了HasCallStack机制。这两种方式之间是否存在一些差异,这些差异会显着影响调试体验?

    最佳答案

    HasCallStack具有一些基本优点:

  • 重量轻,不需要-prof,因此不需要重新编译(因为配置文件代码与非配置文件代码具有不同的ABI)
  • 根据放置HasCallStack约束的位置以及使用withFrozenCallStack的位置,您可以更好地控制调用堆栈中包括的内容,以防止无关的/内部的详细信息显示在轨迹
  • 它允许您使用getCallStack从程序内部访问调用堆栈,因此您可以将其合并到消息中以进行日志记录,异常等
  • 关于haskell - 既然我们在GHC中已经有了 `HasCallStack`,为什么还要提供 `ghc -prof -fprof-auto-top`机制呢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56641366/

    10-09 02:51