Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does HasCallStack still add stack frames when using withFrozenCallStack?

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?

like image 204
Alexis King Avatar asked Nov 28 '16 23:11

Alexis King


1 Answers

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?

like image 90
Joachim Breitner Avatar answered Oct 20 '22 09:10

Joachim Breitner