Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why provide the `HasCallStack` mechanism since we already have `ghc -prof -fprof-auto-top` in GHC?

AFAIK, there are two ways to get a call stack for debugging in Haskell:

  1. add HasCallStack constraint in the code
  2. compile code with ghc -prof -fprof-auto-top

My test code:

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

Following is the call stack I get from both of the above two ways with this test code:

$ 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, the call stack from -prof is already good enough and is easier to use. So I'm wondering why the HasCallStack mechanism is added yet. Is there some difference between these two way which will significantly affect the debugging experience?

like image 848
luochen1990 Avatar asked Jun 18 '19 03:06

luochen1990


1 Answers

HasCallStack has a few basic advantages:

  1. It’s lighter-weight, not needing -prof, so it doesn’t require recompilation (because profiled code has a different ABI than non-profiled code)

  2. It gives you more control over what’s included in the call stack, based on where you place HasCallStack constraints and where you use withFrozenCallStack to prevent irrelevant/internal details from showing up in traces

  3. It allows you to access the call stack from within the program itself using getCallStack, so you can incorporate it into messages for logging, exceptions, and so on

like image 141
Jon Purdy Avatar answered Oct 31 '22 09:10

Jon Purdy