Hat can be used for programs that terminate normally, that terminate with an error message or that terminate when interrupted by the programmer.
The trace consists of high-level information about the computation. It
describes each reduction, that is, the replacements of an instance
of a left-hand side of an equation by an instance of its right-hand
side, and the relation of the reduction to other reductions.
All tools show function arguments in evaluated form, more precisely: as
far evaluated as the arguments are at the end of the computation. For
example, although in a computation the unevaluated expression (map
(+5) [1,2]) might be passed to the function length,
the tools will show the function application as length
[1+5,2+5] or length [_,_].
For example, for the computation of the faulty program
Every reduction replaces an instance of the left-hand side of a program
equation by an instance of its right-hand side. The instance of the
left-hand side ``creates'' the instance of the right-hand side and is
therefore called its parent. With hat-trail you can obtain
the parent of any expression.
(Note: if you cannot see any highlighting in the following diagram,
try changing the fixed-width font in your browser to something like
Courier text.) Each line of the trail is the parent of the highlighted
subexpression directly above it.
Here, the error message is chosen as the starting point, rather
than any of the output. The first trail is therefore
last' [], because its evaluation caused the error message.
The parent of last' [] is
last' [_]. The parent of last' [_]
is last' [_,_]), etc. The parent of the
subexpression 8 is 4*2 whose parent is
xs.
Example session (y/n answers are given by the user):
Using the same example program as above, hat-stack shows
How can I view a trace?
Because the trace describes the whole computation, it is huge. Hat
comes with several tools to selectively view the fragments of the trace
that are of interest. Each tool shows fragments of the computation
in a particular way, highlighting a specific aspect.
Hat-observe is an interactive tool that shows how top-level functions
are used. That is, for a given top-level function name it shows
all the arguments with which it is called during the computation,
together with the respective results.
main = let xs :: [Int]
xs = [4*2,5 `div` 0,5+6]
in print (head xs,last' xs)
last' (x:xs) = last' xs
last' [x] = x
gives the result
(8, No match in pattern.
and the hat-observe tool can be used to explore its behaviour as follows:
$ hat-observe Example
hat-observe 1.12 (:h for help, :q to quit)
hat-observe> main
1 main = IO (print (8,_|_))
hat-observe> print
1 print (8,_|_) = IO (print (8,_|_))
hat-observe> last'
1 last' [8,_,_] = _|_
2 last' [_,_] = _|_
3 last' [_] = _|_
4 last' [] = _|_
hat-observe> :quit
$
Hat-trail is an interactive tool that enables exploring a computation
backwards, starting at the program output or an error message
(with which the computation aborted). This is particularly useful
for locating an error. You start at the observed faulty behaviour
and work backwards towards the source of the error.
Error: -------------------------------------------------------
No match in pattern.
Output: ------------------------------------------------------
(8,
Trail: ---------------------- Example.hs line: 2 col: 12 -----
<- last' []
<- last' [_]
<- last' [_,_]
<- last' [8,_,_]
<- 4 * 2
<- xs
Hat-detect is an interactive tool that enables the semi-automatic
location of an error in a program by answering a sequence of yes/no
questions. Each question asked by hat-detect concerns the reduction
of a redex - that is, a function application - to a value. You have to
answer yes, if the reduction is correct with respect to your
intentions, and no otherwise. After a number of questions
hat-detect states which reduction is the cause of the observed faulty
behaviour - that is, which function definition is incorrect.
$ hat-detect Example
hat-detect 1.12 (:h for help, :q to quit)
1 main = IO (print [3,3,3]) ? n
2 sort [3,2,1] = [3,3,3] ? n
3 insert 1 [] = [1] ? y
4 insert 2 [1] = [2,2] ? n
5 insert 2 [] = [2] ? y
Error located!
Bug found in reduction: insert 2 [1] = [2,2]
For aborted computations, that is computations that terminated
with an error message or were interrupted, hat-stack shows in which
function call the computation was aborted. It does so by showing a
virtual stack of function calls (redexes). Thus, every function
call shown on the stack caused the function call above it. The evaluation
of the top stack element caused the error (or during its evaluation
the computation was interrupted). The stack shown is virtual,
because it does not correspond to the actual runtime stack. The actual
runtime stack enables lazy evaluation whereas the virtual
stack corresponds to a stack that would be used for eager (strict)
evaluation.
$ hat-stack Example
Program terminated with error:
"No match in pattern."
Virtual stack trace: source file/line/col
(last' []) Example.hs 6 16
(last' (5+6:[])) Example.hs 6 16
(last' ((div 5 0):5+6:[])) Example.hs 6 16
(last' (8:(div 5 0):5+6:[])) Example.hs 4 27
main Example.hs 2 1
$
Tutorials and Documentation
You can help us to make Hat a valuable tool for all programmers. Give us feedback! Starting with bug reports you can tell us what you consider as Hat's major deficiencies and which improvements would benefit you most. Thus you may influence our priorities and contribute new ideas.
We are especially looking for medium sized programs (300-3000 lines)
for testing and evaluating Hat. Please
contact us if you want to
contribute a program.
Mailing lists
Because Hat is currently part of nhc98, the nhc mailing lists are the forum for discussing Hat.
Different tracing systems for Haskell give different views of a program
at work. In practice, several views are complementary and can productively be
used together. Until now each system has generated its own trace,
containing only the information needed for its particular view.
Here we present the design of a trace that can serve several views.
The trace is generated and written to file as the computation proceeds.
We have implemented both the generation of the trace and several
different viewers.
Postscript (106 KB)
In this paper we compare three systems for tracing and debugging Haskell programs: Freja, Hat and Hood.
We evaluate their usefulness in practice by applying them to a number of moderately complex programs in which errors had deliberately been introduced. We identify the strengths and weaknesses of each system and then form ideas on how the systems can be improved further.
Postscript (84 KB)