Should exhaustiveness testing be on by default?

Claus Reinke claus.reinke at talk21.com
Thu May 28 10:09:03 EDT 2009


>> One thing that wasn't available when this discussion was last active
>> is 'mapException' (btw, similar to 'catch'/'catches', a 'mapExceptions'
>> would be useful). For instance, appended below is the example from that
>> wiki page, with entirely local transformations to add source locations
>> and to use that info to augment 'ErrorCall' exceptions (we should really
>> augment 'PatternMatchFail' exception messages as well..).
>>
>> $ ghc -e main callstack.hs
>> <interactive>: hd: empty list
>> ("callstack.hs",25)
>> ("callstack.hs",21)
>> ("callstack.hs",16)
>> ("callstack.hs",13)
>
> Your example is giving you the dynamic call stack.  Is that really what you want?  The dynamic 
> call stack will often be quite different from the structure of your program, may well be 
> surprising, and may even differ depending on compiler flags.

Given the source locations, the lexical _position_ is obvious, so for mere
traces, dynamic seems to be the choice (with an option of pseudo-cbv
or the real dynamic stack). What is neither obvious nor provided (dynamic
or static, in any of the proposed stack traces) are the parameters for the
stack. If those are available, as in a debugger, the balance might shift to
favour lexical stack, especially if I'm investigating "what is there?", rather
than "who asked for this, and why?", "where am I?" or "how did I get there?".

Both static and dynamic stack can be useful - different queries, different
answers. And without automated annotation support, it is difficult to test
how useful either stack traces are (apart from: better than nothing;-).

I have the feeling that dynamic stack trace is the right _initial_ answer: if
that induces a need to know about the construction environment, one can
annotate construction Hood-style (preferred), without changing strictness,
or make it strict (not useful if there are many constructions and only one
failing access), so that construction and first observation coincide, or
pretend to have made it strict while evaluating non-strictly (the pseudo-cbv
stack traces). Or one could switch tools, from dynamic to a lexical stack,
and from mere stack trace to debugger (where I would like good lexical
and dynamic stacks with parameters:-).

> My personal preference would be to fix cost center stacks to do the right thing here.  Currently 
> we have +RTS -xc which shows the call stack when an exception is raised, available when your 
> program is profiled. Sometimes it gives odd results because it has bugs, but the idea is that it 
> gives you a *lexical* call stack, which corresponds exactly to the code that you wrote, not the 
> unpredictable evaluation strategy of the compiler.

The more information one can get, the more queries about one's program
one can answer, the more puzzles/bugs one can solve. So, by all means,
lets have lexical stack, pseudo-call-by-value stack, and dynamic stack
choices available. And lexical stack with parameters in the debugger.

Here are the +RTS -xc and mapException outputs together (when I
remove the mapError annotations, only the first <..> is printed, so
that is the part to focus on, the rest is confusion) - they seem to
complement each other (-xc has no locations, but names for the
lexical stack; mapError has no names, but locations for the dynamic
stack; we're still missing the parameters for either stack):

$ ghc --make -prof -auto-all stacktraces.hs
[1 of 1] Compiling Main             ( stacktraces.hs, stacktraces.o )
Linking stacktraces.exe ...

$ ./stacktraces.exe +RTS -xc

-- fib 5
<Main.errorSrc,Main.fib,Main.main,Main.CAF><Main.mapError3,Main.mapError,Main.fib,Main.main,Main.CAF
><Main.mapError3,Main.mapError,Main.fib,Main.main,Main.CAF><Main.mapError3,Main.mapError,Main.fib,Ma
in.main,Main.CAF><Main.mapError3,Main.mapError,Main.fib,Main.main,Main.CAF><Main.mapError3,Main.mapE
rror,Main.fib,Main.main,Main.CAF>fib with non-positive number: 0
("stacktraces.hs",46)
...
("stacktraces.hs",45)
("stacktraces.hs",36)

-- odd_ 5
<Main.errorSrc,Main.odd_,Main.CAF><Main.mapError3,Main.even_,Main.odd_,Main.CAF><Main.mapError3,Main
.odd_,Main.CAF><Main.mapError3,Main.even_,Main.odd_,Main.CAF><Main.mapError3,Main.odd_,Main.CAF><Mai
n.mapError3,Main.odd_,Main.CAF>odd_: no match
("stacktraces.hs",51)
...
("stacktraces.hs",54)
("stacktraces.hs",50)
("stacktraces.hs",38)

-- firstLetters2
<GHC.List.CAF><Main.mapError3,Main.firstLetters2,GHC.List.CAF><Main.mapError3,Main.map_,Main.firstLe
tters2,GHC.List.CAF><Main.mapError3,Main.map_,Main.firstLetters2,GHC.List.CAF><Main.mapError3,Main.m
ap_,Main.firstLetters2,GHC.List.CAF>Prelude.head: empty list
("stacktraces.hs",59)
...
("stacktraces.hs",64)
("stacktraces.hs",63)

> Obviously the down side of CCSs is that you have to compile your code and libraries for profiling, 
> but OTOH you don't have to add any mapExceptions, pragmas, or other explicit annotation stuff. 
> And it could be added to GHCi by default, so when working in GHCi you could have full lexical call 
> stacks for all interpreted code.

If RULES right hand sides could be given access to the source locations
of the code that their left hand sides have matched, the annotation could
be automated, and would seem to be less intrusive than the finding-the-
needle transformation. Also, this seems to be the smallest possible change
to GHC (I just don't know whether it is possible, or how to do it).

But, as I said, the more info, the better. And getting some info without
annotations would be great (currently, +RTC -sc is no use with GHCi,
and even with printE replaced with print, -fbreak-on-error, and :trace
main, the GHCi debugger doesn't seem to offer much help for this
example).

Claus




More information about the Glasgow-haskell-users mailing list