Thoughts on CallStack and withFrozenCallStack

Edward Z. Yang ezyang at mit.edu
Thu Sep 8 04:40:07 UTC 2016


So, I also experimented with setting up this type alias in our
Prelude:

    type IO a = HasCallStack => Prelude.IO a

It hasn't caused many type checking errors, but here are some
observations:

    - It can cause a lot of redundant constraint warnings.
      Often with good reason (sometimes, it indicates somewhere that
      could use the call stack profitably, but isn't at the moment.)
      In cases where it doesn't, I've been suppressing the
      message with "_ = callStack", which works well enough :)

    - When interoperating with functions like 'bracket' which take
      an IO action as an argument, I often need to take
      the type "a -> HasCallStack => Prelude.IO b" into
      "a -> Prelude.IO b" (so that I can use the function
      defined in base.)  This function lets me do the
      conversion:

        withLexicalCallStack :: (a -> WithCallStack (IO b)) -> WithCallStack (a -> IO b)
        withLexicalCallStack f =
            let stk = ?callStack
            in \x -> let ?callStack = stk in f x

      I'm obviously happy to bikeshed names.

    - I don't think adding implicit parameters in this way can ever
      cause us to lose tail-call optimization, but the CallStack
      might grow really big. I guess there isn't any pruning done?
      Essentially, you have to be very careful about IO actions which
      call themselves in loops.

Also, here are some responses to Eric's message:

Excerpts from Eric Seidel's message of 2016-09-07 18:14:16 -0700:
> Inside debug, the top element of the callStack should be the call-site
> of debug, which should be exactly what you want. (NB this is new
> behavior as of GHC 8.0.1, in GHC 7.10.2 you would get an extra frame for
> the occurrence of ?callStack)

Yes, you are right!

> Indeed, this sounds useful. The approach I recently took with adding
> CallStacks to logs was to have the internal functions (eg formatting and
> actually printing the logs) take an *explicit* CallStack, and have the
> external functions take an *implicit* CallStack. Thus the internal
> functions are excluded from the stack. But this does give us slightly
> less reuse as, in your example, you couldn't implement pprDebug in terms
> of debug if you also want to export debug.

Yes, that workaround does work, but I don't like it!

> Good point! Another way this could be useful is if you happen to have a
> frozen CallStack already in-scope at the call-site of debug/pprDebug. In
> that scenario, the call-site of the logging function would be lost, and
> you'd end up printing whatever location was at the top of the frozen
> stack, which would be quite confusing!

Precisely.


More information about the ghc-devs mailing list