Skip to content

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

  1. add HasCallStack constraint for all partial functions in base package
  2. suggest all package maintainers to add HasCallStack constraint for their exported partial functions
  3. provide a compiler option like -fignore-hascallstack to toggle off the effect of HasCallStack constraint 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:

  1. The stack trace generated by -xc contains no line number and column number of the call site.
  2. 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 as HasCallStack.
  3. 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.

Related Works

Edited by Luo Chen
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information