_______________________________________________Motivation
Partial functions in base (especially Prelude) often cause runtime errors and is hard to locate.
(here is a document about the concept of totality and partial function, ignore this if you are familiar with them)
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 theHasCallStack
constraint is totally helpless, because the call stack is cut off by the call tolast
which withoutHasCallStack
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 and prelude 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, and here is a demonstration about that.
On the other hand, is it ever necessary for us to add
HasCallStack
for a total function? Or we can ask, is it possible that a call to a total function cause runtime error? Maybe it’s a NO, since a total function will also crash when the machine is Out Of Memory, but that is also the only situation I can find out. So I suggest that we addHasCallStack
constraint 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 programmers to add
HasCallStack
constraint for their exported partial functions when they release a package- provide a compiler option
-fignore-hascallstack
to toggle off the effect ofHasCallStack
constraint in case somebody need best performanceOther Considerations
How to get a full list of partial functions provided by the base package?
I wanted to provide a full list of partial functions exported by the base package in this post, but I find it is hard, since Haskell have no totality checking mechanism like Idris have, and there are no consistent keyword like “total” or “partial” in document, so it takes a lot of work to list all the partial functions of a package by check every item manually. Maybe we can work on this list later — when it’s turned out to be worth after some discussion.
Here is part of the list that I have tidied for several modules of the base package.
How to encourage all package contributors to obey the rule (see proposal #2)?
I don’t know, but I think there may be some other rules to obey when contributing packages. Maybe we can just add this into the list.
Obviously, the final perfect solution should be let the compiler to check the totality of functions, and automatically add
HasCallStack
for the ones which the compiler cannot confirm it’s totality. But this seems too far away from us, since we still doesn’t have dependent type haskell yet.How to deal with recursive partial functions?
Since the
HasCallStack
constraint affects the performance (not only because of the runtime overhead, but also it’s influence on optimization strategy), It is best not to addHasCallStack
on recursive functions.In most of the cases, we can just check the input shallowly before everything start, just like how we deal with the non-recursive ones.
But in some other cases, we need to go deep to recognize the invalidity of the input. A trivial solution is just perform a deep check before everything start, but the checking phase seems expensive.
The best solution, IMHO, is to make the recursive part a total function, wrap the return value into
Maybe
or some similar things, and the partial version is just the total version combinedfromJust
. In this way, we avoid the single input checking phase and left the error awareing logic where it was before this translation.
Libraries mailing list
Libraries@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries