GHC 8 provides HasCallStack
from the GHC.Stack
module, which allows functions to request a stack frame be recorded when they are called. It also provides the withFrozenCallStack
function, which “freezes” the call stack so that no further frames can be added to it.
In simple scenarios, this works as I would expect. For example:
ghci> let foo :: HasCallStack => CallStack
foo = callStack
ghci> foo
[("foo",SrcLoc {srcLocPackage = "interactive", srcLocModule = "Ghci2", srcLocFile = "<interactive>", srcLocStartLine = 8, srcLocStartCol = 1, srcLocEndLine = 8, srcLocEndCol = 4})]
ghci> withFrozenCallStack foo
[]
When I call foo
normally, I get a stack frame, but when I wrap it with withFrozenCallStack
, I don’t. Perfect. However, when the example gets only slightly more complicated, it stops behaving like I expect:
ghci> let foo :: CallStack
foo = bar
bar :: HasCallStack => CallStack
bar = callStack
ghci> foo
[("bar",SrcLoc {srcLocPackage = "interactive", srcLocModule = "Ghci9", srcLocFile = "<interactive>", srcLocStartLine = 24, srcLocStartCol = 11, srcLocEndLine = 24, srcLocEndCol = 14})]
ghci> withFrozenCallStack foo
[("bar",SrcLoc {srcLocPackage = "interactive", srcLocModule = "Ghci9", srcLocFile = "<interactive>", srcLocStartLine = 24, srcLocStartCol = 11, srcLocEndLine = 24, srcLocEndCol = 14})]
By adding this simple layer of indirection, the stack frame still gets included, despite my use of withFrozenCallStack
. Why?
Conceptually, my understanding of HasCallStack
is that it is like an implicit use of pushCallStack
on the current call stack, and pushCallStack
has no effect on a frozen call stack. Why, then, does withFrozenCallStack
not prevent the above stack frame from being added to the call stack?
In your code, foo
is a static value of type CallStack
. Note that it does not have an HasCallStack
constraint.
It does not matter how and where you use foo
, it will always refer to this particular CallStack
. It does not matter that foo
itself is defined using bar
which uses the HasCallStack
machinery – you could just statically define foo = [("bar",…
.
Try adding HasCallStack =>
to foo
’s type signature. Does it now behave the way you expect?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With