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
HasCallStack
constraint for all partial functions in base package - suggest all package maintainers to add
HasCallStack
constraint for their exported partial functions - provide a compiler option like
-fignore-hascallstack
to toggle off the effect ofHasCallStack
constraint in case somebody need best performance
Other Considerations
-xc
to get a stacktrace?
Why not just use There is indeed some differences between the two stack tracing solutions:
- The stack trace generated by
-xc
contains 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
-xc
not as useful asHasCallStack
. - The
HasCallStack
solution 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.