require-callstack: Propagate HasCallStack with constraints

[ development, library, mit ] [ Propose Tags ]

See the README for more information about this package.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.2.0.0
Change log CHANGELOG.md
Dependencies base (>=4.12 && <5), ghc-prim [details]
License MIT
Author parsonsmatt
Maintainer parsonsmatt@gmail.com
Category Development
Bug tracker https://github.com/parsonsmatt/require-callstack/issues
Source repo head: git clone git://github.com/parsonsmatt/require-callstack.git
Uploaded by parsonsmatt at 2023-09-14T18:15:23Z
Distributions NixOS:0.2.0.0
Downloads 94 total (23 in the last 30 days)
Rating 1.75 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2023-09-14 [all 1 reports]

Readme for require-callstack-0.2.0.0

[back to package description]

require-callstack

Haskell has opt-in call stacks through the use of the HasCallStack constraint. One unfortunate aspect of this design is that the resulting CallStack can be truncated if any function in the call list omits the constraint.

foo :: HasCallStack => Int -> String
foo = error "oh no"

bar :: HasCallStack => Int -> String
bar = foo . negate

baz :: Int -> String
baz = bar . (* 2)

main :: IO ()
main = do
    print $ baz 5

Running this code will fail with an ErrorCall "oh no" exception. The attached CallStack will only mention foo and bar - baz will not be present, nor will main. A truncated CallStack isn't nearly as useful as you might like.

One solution is the annotated-exception library, which can attach CallStack to any thrown exception, and catch is guaranteed to add a stack frame at the catch-site for any exception that passes through. However, it's still nice to have HasCallStack entries on functions - then you get the name of the function, which makes diagnosing an error report easier.

This library introduces a type RequireCallStack. Unlike HasCallStack, this isn't automagically solved - if you call a function that has RequireCallStack in the constraint, you must either call provideCallStack to discharge the constraint, or add RequireCallStack to the signature of the function you're defining.

panic :: RequireCallStack => String -> a
panic = error

foo :: RequireCallStack => Int -> String
foo = panic "oh no"

bar :: RequireCallStack => Int -> String
bar = foo . negate

baz :: Int -> String
baz = bar . (* 2)

main :: IO ()
main = do
    print $ baz 5

This code will fail with a compile-time error:

/home/matt/Projects/require-callstack/test/Main.hs:30:5: error: [GHC-39999]
    • No instance for ‘RequireCallStack.Internal.Add_RequireCallStack_ToFunctionContext_OrUse_provideCallStack’
        arising from a use of ‘bar’
        ....
   |        
30 |     bar . (* 2)
   |     ^^^

The error message, read carefully, will tell you how to solve the issue. If we then write:

panic :: RequireCallStack => String -> a
panic = error

foo :: RequireCallStack => Int -> String
foo = panic "oh no"

bar :: RequireCallStack => Int -> String
bar = foo . negate

baz :: RequireCallStack => Int -> String
baz = bar . (* 2)

main :: IO ()
main = provideCallStack $ do
    print $ baz 5

Then the code compiles and works as expected.