[Well-Typed Blog] Debugging your Haskell application with debuggable

https://www.well-typed.com/blog/2024/12/debuggable/

28 Likes

A question about the Callback example (with functions h1 and h2 and useDebbugable). If, instead of doing putStrLn $ "n = " ++ show n ++ " at " ++ prettyCallStack callStack in g2, I do

g2 :: HasCallStack => Int -> IO ()
g2 n =  do
    throwIO (userError "foo")

Then the exception that bubbles up and is printed doesn’t mention g1 or g2. Shouldn’t it?

HasCallStack backtrace:
  collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:92:13 in ghc-internal:GHC.Internal.Exception
  toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
  throwIO, called at libraries/exceptions/src/Control/Monad/Catch.hs:371:12 in exceptions-0.10.7-3f06:Control.Monad.Catch
  throwM, called at libraries/exceptions/src/Control/Monad/Catch.hs:381:7 in exceptions-0.10.7-3f06:Control.Monad.Catch
  generalBracket, called at src/Debug/Provenance/Scope.hs:44:5 in debuggable-0.1.0-ddebb9d545ffc4eda207d06c6f5bb3e3e629385347299f3f1c960b2508033984:Debug.Provenance.Scope
  scoped, called at app/Main.hs:27:8 in deb-0.1.0.0-inplace-deb:Main
  h2, called at app/Main.hs:24:8 in deb-0.1.0.0-inplace-deb:Main
  h1, called at app/Main.hs:30:17 in deb-0.1.0.0-inplace-deb:Main
  useDebuggable, called at app/Main.hs:37:5 in deb-0.1.0.0-inplace-deb:Main

Edit: g1 and g2 are also missing in the withoutDebuggable case, if I throw the exception and don’t use Callback:

  collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:92:13 in ghc-internal:GHC.Internal.Exception
  toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
  throwIO, called at libraries/exceptions/src/Control/Monad/Catch.hs:371:12 in exceptions-0.10.7-3f06:Control.Monad.Catch
  throwM, called at libraries/exceptions/src/Control/Monad/Catch.hs:381:7 in exceptions-0.10.7-3f06:Control.Monad.Catch
  generalBracket, called at src/Debug/Provenance/Scope.hs:44:5 in debuggable-0.1.0-ddebb9d545ffc4eda207d06c6f5bb3e3e629385347299f3f1c960b2508033984:Debug.Provenance.Scope
  scoped, called at app/Main.hs:13:8 in deb-0.1.0.0-inplace-deb:Main
  f2, called at app/Main.hs:10:8 in deb-0.1.0.0-inplace-deb:Main
  f1, called at app/Main.hs:33:21 in deb-0.1.0.0-inplace-deb:Main
  withoutDebuggable, called at app/Main.hs:38:5 in deb-0.1.0.0-inplace-deb:Main

So it has to do with how exceptions bubble up and interact with HasCallStack, not with Callback.

Edit#2: If I remove the use of “scoped”, g1 and g2 start to appear :sweat_smile:

f2 :: HasCallStack => (Int -> IO ()) -> IO ()
-- f2 k = scoped $ k 1
f2 k = k 1

h2 :: HasCallStack => Callback IO Int () -> IO ()
-- h2 k = scoped $ invokeCallback k 1
h2 k = invokeCallback k 1

HasCallStack backtrace:
  collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:92:13 in ghc-internal:GHC.Internal.Exception
  toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
  throwIO, called at app/Main.hs:22:5 in deb-0.1.0.0-inplace-deb:Main
  g2, called at app/Main.hs:17:8 in deb-0.1.0.0-inplace-deb:Main
  g1, called at app/Main.hs:35:24 in deb-0.1.0.0-inplace-deb:Main
  withoutDebuggable, called at app/Main.hs:40:5 in deb-0.1.0.0-inplace-deb:Main
1 Like

Thanks for sharing! There are some really interesting features to be unlocked with HasCallStack. Here’s one that I’ve just written up: Domain errors with HasCallStack

@adamgundry Very cool! Are there any plans to upstream that into base? I’d imagine you’d like to get some feedback from users first?