Debugging

Malcolm Wallace Malcolm.Wallace@cs.york.ac.uk
Thu, 28 Aug 2003 11:57:41 +0100


Konrad Hinsen <hinsen@cnrs-orleans.fr> writes:

> Particular difficulties in Haskell:
> - Conditional tracing. Suppose a function is called 1000 times but I am
>   interested in a particular intermediate result only when the third
>   argument is greater then three.
> - Tracing a part of a value, say the first five elements of a list or the
>   even-numbered elements of an array.

The Hat solution is to trace everything, and then use a specialised
query over the trace to narrow it to just the points you are interested
in.  At the moment, the hat-observe browser is the nearest to what you
want - like HOOD, it permits you to see the arguments and results of
a named function call, but additionally you can restrict the output
to a named context, narrowed by normal Haskell-style patterns for
the arguments and results.  e.g.

    foo True [2,3,_] = Branch _ (Leaf _)  in  g

asks for all applications of foo called from g, where foo's first
argument is True, its second argument is a list of three elements
beginning with 2 and 3, and the result of the call is a tree with a
leaf on the immediate right of the top-most branch.

Over the next year, we hope to have a research student looking at
extending the query language of hat-observe.  For instance, I can
imagine adding Haskell-style guards, which would make it possible to
express your first example as

    foo _ _ x | x > 3		-- third argument must be greater than three

Another idea is to permit real Haskell expressions to post-process the
result of the trace query, rather like meta-programming.  So your second
example might look something like

    take 5  [| foo 3 |]		-- show only the first five elements of the result list

    evens [| mkarray _ |]	-- select and print only the even-indexed array elements
      where evens arr = show (map (\i->arr!i) [b, succ(succ(b)) .. t])
                      where b = fst (bounds arr)
                            t = snd (bounds arr)


> If it takes complex data structures as input, then the only reasonable way
> to provide that input may be calling it from another piece of the code.

Exactly, and this is where Hat is most useful, because it traces the
real run of the program, recording all the intermediate data structures.

> > QuickCheck gives a much better indication of correctness with much less
> > manual labor.

I can recommend QuickCheck as well, although Koen or John may wish
to comment on how suitable they think it would be for numerical work.

QuickCheck and Hat can be made to work together nicely.  There is a
version of QuickCheck in development which works by first running
the ordinary program with lots of random test data.  If a failure
is found, it prunes the test case to the minimal failing case, and
passes that minimal case to a Hat-enabled version of the program,
which can then be used to investigate the cause of the failure.

> > the type level). Do you want to avoid ANY code changes?
> 
> Not necessarily, but I'd prefer them to be local, at least restricted to one
> module. I ended up introducing (minor) bugs into rather unrelated code 
> through typos made when adding "Observable" constraints.

The need to alter the program under test is the biggest disadvantage of
HOOD in my opinion since, as you have shown, it can lead to new bugs.
Hat largely avoids this pitfall.  I believe there is also a version of
HOOD more fully integrated with Hugs, which removes the need to derive
Observable instances and add extra Observable constraints on functions.

Regards,
    Malcolm