Proposal: add HasCallStack for all partial functions
Motivation
Partial functions in base (especially Prelude) often cause runtime errors and is hard to locate.
For example, consider the following piece of code:
import GHC.Stack
foo :: HasCallStack => [Int] -> Int
foo xs = last xs + 1
xs :: [Int]
xs = []
main :: IO ()
main = do
print $ foo xs
In this case, the error message will tell nothing about foo, and the HasCallStack constraint is totally helpless, because the call stack is cut off by the call to last which without HasCallStack constraint.
My current workaround is define my own wrapper functions with HasCallStack constraint for some mostly used partial functions to make them traceable, and use the wrapper (the traceable version) whenever I need them.
e.g.
last' :: HasCallStack => [a] -> a
last' xs = case xs of [] -> error "abuse last"; _ -> last xs
So, IMHO, if our goal is to make errors in haskell traceable, then only providing HasCallStack mechanism is not enough, we have to provide traceable base package at the same time.
Further more, all untraceable partial functions are considered to be harmful, and should not be exported by any package. Because an improper call to an untraceable partial function will cut off the call stack (here is a demonstration about that).
On the other hand, it is never necessary for us to add HasCallStack for a total function, so I suggest that we add HasCallStack constraint for (and only for) partial functions, and IMHO this could be a good balance for better debugging experience and less runtime overhead.
Proposal
- add
HasCallStackconstraint for all partial functions in base package - suggest all package maintainers to add
HasCallStackconstraint for their exported partial functions - provide a compiler option like
-fignore-hascallstackto toggle off the effect ofHasCallStackconstraint in case somebody need best performance
Other Considerations
Why not just use -xc to get a stacktrace?
There is indeed some differences between the two stack tracing solutions:
- The stack trace generated by
-xccontains no line number and column number of the call site. - When we run a long running program, such as a web service, it is not easy to reproduce the issue, most of the time, you have no chance to recompile or restart your program to debug, all you can rely on is the printed logs, this makes
-xcnot as useful asHasCallStack. - The
HasCallStacksolution is much more friendly for learners, you don't need to know any tricks to get a stack trace printed, this reason seems ridiculous but IMHO it indeed affects the learning curve of Haskell.
Is it a consistent design?
Some people may feel it weird if we just add HasCallStack for some functions but not the others, but I think it is consistent since we have a concrete rule to decide whether we need to add HasCallStack for a function -- by it's totality.
What about the performance issue?
I think we need a benchmark test before we weigh up the advantages and the disadvantages. It would be good news if there are some available testsuites for this purpose.