-
Notifications
You must be signed in to change notification settings - Fork 183
Added HasCallStack to partial functions #493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
You're going to need to wrap all those pieces in CPP restricting in to appropriate __GLASGOW_HASKELL__
versions.
Is it a __GLASGOW_HASKELL__
version or is it MIN_VERSION_base(4,9,0)
?
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The questions about update and deletion apply across the board.
Data/Map/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to just make this function total by making an update at a bad index do nothing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely a more interesting question than just where to add HasCallStack
and might make more sense for a different issue/PR. Personally, I'm not sure I've ever used the ***At
functions, so I'm not really the target audience to be weighing in on this decision. Is there a way you could get community feedback about such a change? Maybe Haskell-cafe?
Data/Map/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here.
Data/Map/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ehhhhh... Not sure we should bother with this one. mergeWithKey
is for users who don't mind the risk of being shot in the foot.
Data/Sequence/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no no. This is an internal function and is only used when we know it will succeed.
Data/Sequence/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no no. This is an internal function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the call stack grow through the recursion here or elsewhere? If so, that's a big problem. You can check the Core to be sure. We know there are no Nil
s except at the root, but GHC does not! If the stacks build, you'll need to restructure the functions to fix that. Watch out for performance. If the times for the current benchmarks exercising these functions are too short to trust, consider adding more benchmarks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, yes it does. I didn't realize it would do that. The fix is to use an internal go
function. I'll update the PR with that in a moment.
On a related note, I realized that my HasCallStack
for Map.!
was wrong because Map.!
calls find
which actually throws the error. Is there a reason it uses find
rather than lookup
? My inclination is to remove find
altogether (it's only used by !
and looks identical other than the Maybe
wrapper) and replace with lookup
---then !
can call error
itself, which should then make HasCallStack
work the way we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I just noticed the [Note: Local 'go' functions and capturing]
-- I'll read that and try to make sure I'm not doing anything stupid.
Data/Sequence/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an internal function and shouldn't get a call stack. It's used to implement the IsList
instance. Arguably, the method it's used to implement should get a call stack, but that method isn't really intended to be called directly in user code anyway, and GHC will always call it correctly.
Data/IntMap/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using lookup
will incur a Just
allocation. One possible solution (for very recent GHC versions only) would be to define a lookup#
function producing an unboxed sum:
type Maybe# a = (# Void# | a #) pattern Nothing# :: Maybe# a pattern Nothing# <- (# _ | #) where Nothing# = (# void# | #) pattern Just# :: a -> Maybe# a pattern Just# a = (# | a #) lookup# :: Key -> IntMap a -> Maybe# a lookup# k m = ... lookup k m = case lookup# k m of Nothing# -> Nothing Just# a -> Just a
The (potentially) great thing about this is that the Just
gets applied on the "outside", so inlining and the case-of-case transformation will end up making it go away altogether:
m ! k = case (case lookup# k m of Nothing# -> Nothing Just# a -> Just a) of Nothing -> error ... Just v -> v -- ==> case-of-case, case of known constructor m ! k = case lookup# k m of Nothing# -> error ... Just# a -> a
Unlike a CPS version, this theoretically helps user-written code as well. However, I have not actually tried benchmarking anything like this. And the CPP is potentially troublesome too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems cool -- it seems like it could be a fairly big change that would even prevent an allocation when one uses lookup
(or related functions that return Maybe
values) and immediately unwraps -- but it's a bit big for what I was hoping to contribute just now (also, a bit over my head). I think I'll take the easy way out and go back to using a find
function to get a result without the extra allocation. Perhaps my next PR will be to overhaul the backend to use unboxed sums where possible :).
Data/Map/Strict/Internal.hs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this actually give the right call stacks? Thinking about this more carefully, I suspect you want to check if the operation will succeed, and only then call a go
function that takes no call stack. The alternative would be to call the go
function using withFrozenCallStack
, which I suspect will be more expensive. Benchmarks may tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My tests show that, by default, functions like go
do not have the HasCallStack
constraint unless it is explicit in their type signatures. Therefore, this seems to give the right call stack. There's no need to check if the operation succeeds or not first, and because go
doesn't have the constraint, there's no need for withFrozenCallStack
.
Are you making any progress? I'd love to get this into the next release, but that's probably going to happen in the morning!
I'm sorry, but I got caught up in other work this past week and haven't worked on this at all. I guess it will have to wait until the next release :/.
More generally, I must confess that I really don't know how to check Core. I think I know how to look at Core (-ddump-simpl
?), but I don't know what to do with it or how to tell if it's good or bad.
-ddump-simpl
indeed. I suggest that for a while you should add -dsuppress-all -dno-suppress-type-signatures
. You want to look for fishy things like recursively growing call stacks.
Separately, you should test each function with bad arguments of all relevant sorts and make sure the call stack indicates that the problem is with the bad code that called the partial function, not the partial function itself.
bollu
commented
Dec 24, 2018
What is the status on this? I wound up writing custom wrappers around some of the functions in Data.Map
for stack traces, but it would be awesome if this were upstreamed.
Is it necessary to pay a performance cost? Can we not expose a Data.Map.{Strict, Lazy}.Debug
that contains the functions that expose debug info?
As discussed on Issue #489, this PR adds
HasCallStack
to the constraints of partial functions. I think I got all of the relevant partial functions, but I'd be happy for someone to double check that I didn't miss anything.I ran the benchmarks (
stack bench
) before and after these changes and have attached them (before_hascallstack.bench.txt, after_hascallstack.bench.txt). I scanned through the results, trying to pay attention to tests on partial functions specifically, but I didn't notice any changes (outside of expected deviations within the margin of error).Please let me know if there's more that I can do to help get this PR successfully merged.