Proposal: add HasCallStack for all partial functions in base

Simon Jakobi simon.jakobi at googlemail.com
Sun Jun 16 16:20:27 UTC 2019


I like the idea of marking partial functions with HasCallStack.
But I'd like to point out that we also have class methods where some but
not all implementations are partial, such as foldr1 in Foldable.

Should methods like these also get a HasCallStack constraint?

Am So., 16. Juni 2019 um 14:30 Uhr schrieb LuoChen <luochen1990 at gmail.com>:

> Motivation
>
> Partial functions in base (especially Prelude) often cause runtime errors
> and is hard to locate.
>
> (here
> <https://gist.github.com/luochen1990/6fcbb0fa4c0c7caa1b242188eb586d1f> 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 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 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
> <https://gist.github.com/luochen1990/94179a2492ff7d1ca45153645f1bb449>.
>
> 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 add HasCallStack
> constraint 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 programmers to add HasCallStack constraint for their
>    exported partial functions when they release a package
>    3. provide a compiler option -fignore-hascallstack to toggle off the
>    effect of HasCallStack constraint in case somebody need best
>    performance
>
> Other 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
> <https://gist.github.com/luochen1990/5b7a0844701413486da595b9b997f3c2> 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 add HasCallStack 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 combined fromJust. 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 at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20190616/04614ee9/attachment.html>


More information about the Libraries mailing list