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