Seeing as Haskell is apparently such a popular language these days,
I don't suppose a working debugger would be too much to ask for, would it?
Or even just a decent call stack trace when a program terminates with an exception?
In case you're wondering, yes I have already tried using Hat and Buddha.
But I'm trying to debug a real application, not a toy one, and neither Hat
nor Buddha support enough of Haskell. Buddha doesn't even come close,
as far as I can tell, and Hat lacks support for library modules such as Data.Word.
I've also tried using ghc's profiling to get stack traces, but that doesn't
work very well either. Firstly, the stack traces that you get only include
function names, not line numbers. Secondly, the stack traces are often
incomplete, for reasons that I don't fully understand. One likely cause is
tail call optimization. (Is there any option to switch off tail call optimization?
I didn't see any such option in the ghc man page.)
Thirdly, profiling seems to be incompatible with the use of ghci; there
doesn't seem to be any easy way to build a workspace so that you can
get stack traces and use ghci in that workspace at the same time.
And ghc is slow enough (even on a 3.2GHz Pentium 4) that recompiling
the whole workspace is highly unpalettable.
There's a hell of a lot of languages out there that _do_ support decent
stack traces when an exception is thrown.
What's the point of using a high-level functional language if it means
you're stuck with poor library support and/or stone-age tools?
\end{gripe}
--
Fergus J. Henderson | "I have always known that the pursuit
Galois Connections, Inc. | of excellence is a lethal habit"
Phone: +1 503 626 6616 | -- the last words of T. S. Garp.
_______________________________________________
Haskell mailing list
Has...@haskell.org
http://www.haskell.org/mailman/listinfo/haskell
> Seeing as Haskell is apparently such a popular language these days,
> I don't suppose a working debugger would be too much to ask for, would it?
Hah. You're not supposed to debug, just stare at the code until
you become enlightened (why did you think it was called Buddha,
anyway)? :-)
> I've also tried using ghc's profiling to get stack traces, but that doesn't
> work very well either.
So you know about +RTS -xc.
> Thirdly, profiling seems to be incompatible with the use of ghci; there
> doesn't seem to be any easy way to build a workspace so that you can
> get stack traces and use ghci in that workspace at the same time.
You can compile with: -prof -auto-all -hisuf p.hi -osuf p.o
This will create separate object and interface files for profiling. I
haven't really tried juggling GHCi in between, but I think it would
ignore those.
(Or did you want GHCi to use the profiling info to get the stack
traces?)
> And ghc is slow enough (even on a 3.2GHz Pentium 4) that recompiling
> the whole workspace is highly unpalettable.
Compile without -O? Avoid large source files, in particular with
large embedded data structures? But yes, it's slow.
-kzm
--
If I haven't seen further, it is by standing in the footprints of giants
http://www.cambridge.intel-research.net/~rennals/hw2003.pdf
Unfortunately, HsDebug depends on Optimistic Evaluation, and it seems unlikely
that Optimistic Evaluation will appear in a release version of GHC (too hard
to maintain, and I've gone off to work for Intel Research).
HsDebug is a GDB-style debugger which takes advantage of the fact that
Optimistic Evaluation makes programs evaluate in a largely-strict evaluation
order. It also uses a trick called "transient tail frames" to allow tail calls
to be visible in stack traces.
HsDebug can debug any GHC-compilable Haskell program, including GHC itself.
-Rob
> I wrote such a debugger as part of my PhD work. It is called
> "hsdebug" and you can read the Haskell Workshop paper on it here:
>
> http://www.cambridge.intel-research.net/~rennals/hw2003.pdf
>
> Unfortunately, HsDebug depends on Optimistic Evaluation, and it seems
> unlikely that Optimistic Evaluation will appear in a release version
> of GHC (too hard to maintain, and I've gone off to work for Intel
> Research).
>
> HsDebug is a GDB-style debugger which takes advantage of the fact that
> Optimistic Evaluation makes programs evaluate in a largely-strict
> evaluation order. It also uses a trick called "transient tail frames"
> to allow tail calls to be visible in stack traces.
>
> HsDebug can debug any GHC-compilable Haskell program, including GHC
> itself.
We would be delighted if someone would take Robert's hsdebug and port it
to the non-speculative version of the compiler. In fact, this is one of
the items on the GHC Task list:
http://sourceforge.net/pm/task.php?func=detailtask&project_task_id=98=
684
&group_id=8032&group_project_id=31802
It would make a great student project, or a summer hack for someone with
lots of spare time...
Cheers,
Simon
I have read the Robert's paper but I can't understand
how hsdebug can work with lazy evaluation.
Krasimir
__________________________________
Do you Yahoo!?
Yahoo! Mail is new and improved - Check it out!
http://promotions.yahoo.com/new_mail
You either end up single stepping through a million lines of code,
or you find the error is of a complex non-localised kind anyway.
I find inserting my own debug code into an application to be a much
more fruitful way of keeping track of errors. Using conditional
compiliation all this extra code can be removed from a finished
app.
The only problem with this approach in Haskell is that you sometimes
have to lift a function into the IO monad for debugging...
Keean.
I agree.
> In case you're wondering, yes I have already tried using Hat and Buddha.
> But I'm trying to debug a real application, not a toy one, and neither Hat
> nor Buddha support enough of Haskell.
Well, Hat only promises Haskell'98, and it actually delivers somewhat
more. It is too much to ask for application developers to stick to
agreed standards? :-)
In fact, I often use Hat to debug real applications, such as the nhc98
compiler, HaXml library code, cpphs, etc.. Where an application demands
more of Hat than it currently delivers, we try to extend Hat to cope.
We just need two simple things: user feedback, and resources. :-)
> There's a hell of a lot of languages out there that _do_ support decent
> stack traces when an exception is thrown.
As I'm sure you know, getting a meaningful stack trace from a lazy
program is rather difficult. The actual stack at the time of failure
is next to useless. You really want to see a reconstruction of a
strictified evaluation order. That's why HsDebug uses optimistic
evaluation, and why Hat builds redex trails.
> What's the point of using a high-level functional language if it means
> you're stuck with poor library support and/or stone-age tools?
If companies are willing to invest in development, they will get the
tools they want.
Regards,
Malcolm
debugs logs go to a file, grep and awk let you quickly find things
>3) A complex library written by someone else which contains a bug or makes
before calling out print a funtion trace like:
function "f" called from "reified location" with arguments ...
returned "value"
>4) this is what assert statements are for as in:
assert (x>0)
function-which-assumes x>0
For large project development - the development cycle should go
along these lines...
1) functional specification
2) modularise
3) design interfaces for components
4) build testbench for components
5) code components...
I have missed out the iterations for simplicity. Note the testbench
should be built before the module - this includes analysis for
linear regions and corner cases... The testbench will log each test
along with the results.
Testbenches should be provided with third party components - if they
are not they should be built as part of the development process.
I find if you do the above and put adequate debug logging in your
code the need for a separate debugger is minimal...
To get slightly more technical all the debugger does is run the code
with full internal visibility (all states visible) - by adding
debug statements you can increase visibility to the same level.
logging all these debug outputs to a file gives you the same
observability as single stepping, but it is much quicker to
run the program at full speed and search the file. Prining out
the full program state (or scoped variables) at any point is
equivalent to a breakpoint.
Consider this as a suggestion of how to cope without a debugger - until
you find one you like...
Keean.
Or you find that inserting a breakpoint (or waiting to hit an exception),
examining data structures which are either in scope or in the scope of the
callers(*) tells you exactly what has gone wrong. When writing in other
languages, I find this is the most common scenario - though, of course, it is
the non-localised heisenbugs that I remember most vividly.
* By 'callers' I mean the code that contains the application of the function
to an argument not the code that first forced evaluation of the resulting
application node. For example, if I have 'let f x = (x+1,x-1) in fst(f 3)',
then the caller of + is f not fst or the entire expression.
> I find inserting my own debug code into an application to be a much
> more fruitful way of keeping track of errors.
Unless, of course, you have:
1) A large program operating on a large body of data that takes a long time to
hit the bug.
2) A bug that only shows up some of the time and the program is interactive
which makes it hard (or tedious) to repeat the bug.
3) A complex library written by someone else which contains a bug or makes
assumptions about the arguments passed to it.
4) A division by 0, call to head, etc. where the resulting error message
provides absolutely no guidance about where the error is in your 30,000 line
program forcing you to grep for all uses of the offending operation both in
your program and in any libraries it uses.
[I may have left out a few other scenarios where being able to investigate the
problem interactively and without already knowing where to look and what to
look for is useful.]
> The only problem with [inserting debugging code by hand] in Haskell is
> that you sometimes have to lift a function into the IO monad for
> debugging...
I find the trace function works very well for this. It's interactive
debugging that I really miss.
--
Alastair Reid
Also, is there a way to get the typesystem to
tell you which functions may fail i.e. which
functions have failMsg as an implicit parameter?
-Alex-
_________________________________________________________________
S. Alexander Jacobson mailto:m...@alexjacobson.com
tel:917-770-6565 http://alexjacobson.com
On Thu, 24 Jun 2004, Simon Peyton-Jones wrote:
> Switching on profiling (-prof -auto-all) does essentially just this. The
> cost centre stack is just like an implicit parameters, only one that
> does not show up in the types.
>
> Since there really is some extra parameter passing going on, you really
> do need to recompile (all) the code. That's a nuisance, but it's a
> price that I can't see how to avoid in a call-by-need language.
>
> Simon
>
> | -----Original Message-----
> | From: haskell...@haskell.org [mailto:haskell...@haskell.org]
> On Behalf Of Alastair Reid
> | Sent: 23 June 2004 21:44
> | To: S. Alexander Jacobson
> | Cc: has...@haskell.org
> | Subject: Re: [Haskell] modern language design, stone age tools
> |
> | On Wednesday 23 June 2004 20:38, S. Alexander Jacobson wrote:
> | > It would be really nice if you could pass an
> | > error message down to every function that might
> | > fail. e.g. using implicit parameters*:
> | >
> | > myFunc 0 x = head x with ?failMsg="myfunc 0 caused the error"
> |
> | Interesting. Two variations on that:
> |
> | 1) Getting a 'stack' trace (i.e., a list of ancestors) can be more
> | useful than just knowing the immediate ancestor so you might want
> | myFunc to also take an implicit parameter and to add that
> | parameter to what it says:
> |
> | myFunc 0 x = head x
> | with ?failMsg="myfunc 0 caused the error but myFunc was called
> because "
> | ++ ?failMsg
> |
> | [I can't remember if with acts as a let or a letrec. The intention
> is
> | to use the ?failMsg passed implicitly to myFunc]
> |
> | 2) If you don't want to put errors in the type system, you could
> instead use
> | exceptions something along the lines of:
> |
> | myFunc 0 x = mapException
> | (\ err -> show err ++ "when invoked by myFunc 0")
> | (head x)
> |
> | [Not quite type correct but hopefully clear enough. The idea is to
> combine
> | the exception value returned with some extra information about the
> context
> | with the idea that whoever called myFunc might add extra
> information.
> | Ideally, the Exception type would be recursive so we could build
> chains of
> | exceptions without having to use Show.]
Urgh, so the automatic annotation I suggested suffers from two problems:
1) It gives the call by name call stack when we usually want the call by value
call stack.
2) It would have to be done in a type-sensitive way. Functions which return
an IO result need to be annotated with mapExceptionIO while others need to be
annotated with mapException. (For that matter, anyone defining 'sum = foldr
(+) 0' will want a 3rd variant of mapException for use with CAFs whose value
is a 1,2,3,etc-argument function.) Hmmmm, I'll bet Oleg can come up with a
cunning pattern of type classes for this purpose :-).
>>Thank you for the programming practice recomendation,
> Sorry if it seemed like that...
Huh - I thought that was sincere; certainly I am happy to learn about
sensible (or not) practices that others find useful.
> but I do feel that 'commercial quality' code should be bullet proof,
> and of a different calibre to code for personal use, or academic
> research.
I'm not so sure adding stuff to handle exceptions/errors is the way to
go. I've developed 'commercial quality' code (in the 'real world',
too), and the exception handling stuff tended IMO to be extensive,
intrusive, and serving to cover up errors, rather than fix them.
There are just too many things that can conceivably go wrong to adress
them all in advance.
Better programmers than me will probably disagree, but for now I've
filed exception handling together with shared-everything threading
(C/POSIX-style) as too difficult to get right to be worthwhile.
-kzm
--
If I haven't seen further, it is by standing in the footprints of giants
>> [...]
>> 2. Use the mapException trick to annotate exceptions as they
>> travel up the stack (see Alastair Reid's message). [...]
>> (2) requires that you add lots of annotations to your code, so it's
>> not entirely satisfactory for that reason.
>
> Would it be possible to generalise ghc's -prof-all mechanism so that
> annotations could be added automatically. For example, suppose I have
> an annotation function
>
> Debug.Callstack.callstack :: ModuleName -> LineNumber -> a -> a
>
> Then, if I compile with:
>
> ghc -annotate=Debug.Callstack.callstack Foo.hs
>
> then ghc could add
>
> import Debug.Callstack(callstack)
>
> to the import list and transform every top level function body
> as follows:
>
> foo (x:xs) = ..
> foo [] = ...
> =>
> foo arg = callstack
> "Foo" -- module name
> 42 -- line number
> (foo' arg)
> foo' (x:xs) = ..
> foo' [] = ..
Yes, this could be done. I should have mentioned though: this technique
gives you the lazy call stack, whereas what you probably want is the
strict call stack (which is what GHC's profiler tries to give you).
Getting the lazy call stack is easy from a debugger, we don't need to
annotate the program to do that, but we might want to add source
locations in the form of debug info to the object file in a similar way
to C compilers.
It may be that giving programmers access to the lazy call stack is a
heck of a lot better than nothing.
Cheers,
Simon
1) this works:
main :: IO ()
main = do
a <- getLine
hPutStrLn $ showInt (fst . head . readDec $ a :: Int)
`Exception.catch` (\e -> throw $ ErrorCall $ showString "Urk :" $ show e)
2) this doesn't work:
main :: IO ()
main = mapException (\x -> ErrorCall $ showString "Urk :" $ show x) $ do
a <- getLine
hPutStrLn $ showInt (fst . head . readDec $ a :: Int)
Keean. (using GHC-6.2)
> On 23-Jun-2004, MR K P SCHUPKE <k.sc...@imperial.ac.uk> wrote:
>> This may not be the right answer to the question (which is of
>> course lets write a debugger) - But I have never used a debugger,
>> and find them more or less the most unfriendly and useless things
>
> So how do you debug problems like "Prelude.head: empty list"
> in large programs?
The two most useful techniques, both have which have already been
suggested by others are:
1. Compile with -prof -auto-all and run with +RTS -xc.
2. Use the mapException trick to annotate exceptions as they
travel up the stack (see Alastair Reid's message).
(1) doesn't work that well, as you noticed. For example, sometimes the
exception will stop at a CAF, due to the way cost centres work in GHC.
I'd really like us to do better here - keeping track of the call stack
in a lazy higher-order language has turned out to be surprisingly
tricky.
(2) requires that you add lots of annotations to your code, so it's not
entirely satisfactory for that reason.
Even if we had an interactive debugger, getting a good call stack is
still going to be a hard problem.
Cheers,
Simon
Would it be possible to generalise ghc's -prof-all mechanism so that
annotations could be added automatically. For example, suppose I have
an annotation function
Debug.Callstack.callstack :: ModuleName -> LineNumber -> a -> a
Then, if I compile with:
ghc -annotate=Debug.Callstack.callstack Foo.hs
then ghc could add
import Debug.Callstack(callstack)
to the import list and transform every top level function body
as follows:
foo (x:xs) = ..
foo [] = ...
=>
foo arg = callstack
"Foo" -- module name
42 -- line number
(foo' arg)
foo' (x:xs) = ..
foo' [] = ..
--
Alastair
Note that pattern matching rather than deconstruction functions have a
number of benefits, not just relating to error messages, consider two
functions which use the head of their argument.
f xs = ... head xs ...
g (x:_) = ... x ...
now, g is superior to f in several ways,
1) the error message generated if the pattern fails will have the file
name, line number and name of function
2) everything but the head of xs may be garbage collected right away
since there is no reference to the rest of the list! This can cure some
nasty space leaks and is vital in certain cases.
3) even the simplest compiler will realize g is stirct in its first
argument and take advantage of the numerous optimizations that entails.
A good compiler may figure out 2 and 3 with f, but it can't always, and w=
hy
take the chance it won't? All of these benefits apply to deconstructing
more complicated types too, so using pattern matching for deconstruction
is just a good habit to get into.
John
--
John Meacham - ⑆repetae.net⑆john⑈
maybeHead :: [a] -> Maybe a
maybeHead (a:_) = Just a
maybeHead _ = Nothing
etc...
The the calling function can do something like:
case maybeHead x of
Just y -> <expr y>
Nothing -> fail "failed in..."
This way you can pass the failure back up to somewhere where
its meaningful.
In my opinion you should either be encoding failure in the
return type, or using exceptions... anything else is just
bad coding.
Finally use guards (that can be conditionally compiled out)
before functions that might fail:
if null x then fail "sesible error message" else ...
Keean.
So how do you debug problems like "Prelude.head: empty list"
in large programs?
--
Fergus J. Henderson | "I have always known that the pursuit
Galois Connections, Inc. | of excellence is a lethal habit"
Phone: +1 503 626 6616 | -- the last words of T. S. Garp.
--
Alastair Reid
Yes, I do something very similar to this in ginsu, what would be really
really nice is if Exception were extended with
AnnotatedException String Exception
and the various routines like isIOError were modified to 'look through'
these annotations.
adding stack traces just where you think it might be useful becomes
quite easy then.
John
--
John Meacham - ⑆repetae.net⑆john⑈
enable template haskell and use $head where $head is defined to generate
'head' with the error annotated with the current line number and file.
John
--
John Meacham - ⑆repetae.net⑆john⑈
Actually I have used Haskell in commercial projects... and am continuing to
do so. My development environment is Vim and GHC. Same one I have used for
C/C++ VHDL Perl, and anything else...
It is not that lack of GUI tools, but just the lack of coders that is the
problem (or the price)... With my consultants hat on, looking at starting
a project we ask how much is it going to cost to get a coder fluent in this
language and how easily can one be found at short notice.
I just think the intellectual barriers to becomming a Haskell coder are
a bit higher (understanding monads etc) - meaning less people can do it.
I know plenty of real world developers using other languages that also
do not use debuggers very much if at all.
The only fix I can see it when the world wakes up to the fact that side
effects and weak typing lead to buggy programs, and realise that is
unacceptable. Unfortunately in business a buggy program is often good
enough. Basically if it is delivered on time is can be full of bugs -
but the customer won;t notice them all right away - so you can get
paid, and just fix those the customer finds really annoying. This can
be cheaper than trying to produce 100% correct code.
Keean.
Well, there are quite a lot of such occurrences in the code that I'm working
on:
bash$ find . -name \*.hs -o -name \*.lhs | xargs grep -w head | wc -l
130
Replacing all of those occurrences by hand is going to be very very
tedious and somewhat time-consuming. Doing it with a script would be
better, but that's not a trivial task.
Even once that is done, there's no guarantee it will actually help to
find the problem. After all, the problem might well be arising from a
call to "head" in one of ghc's standard libraries:
bash$ find ~/ghc6-6.2/hslibs \*.hs -o -name \*.lhs | xargs grep -w head | wc -l
104
So not only do I have to edit my own code, and the libraries written by
my colleagues, I also need to edit the ghc library code, and figure out
how to build and reinstall the ghc libraries. That could take a long time.
After all that, hopefully I will finally know which function called
"head" with an empty list. But even then there's still no guarantee
that I've actually found the source of the problem; the real problem
might be in that function's caller, or the caller's caller, etc. So I
might have to go through the whole process again.
> (it is rather sad that this is the best approach i could come up
> with...basically tries to get around the [lack of/uselessness of/inability
> to use with ghci] stack traces)
Yes :-(
--
Fergus J. Henderson | "I have always known that the pursuit
Galois Connections, Inc. | of excellence is a lethal habit"
Phone: +1 503 626 6616 | -- the last words of T. S. Garp.
Hmmm... chickens and eggs. I don't see a lot of *companies* using Haskell
right now. And probably they won't until the tools they want are available
(and more...) :-(
I think the biggest problem with Haskell debugging is visibility of
intermediate values. I sometimes end up planting Debug.Trace calls in the
code, but that can be clumsy -- one of the reasons I develop in Hugs rather
than GHC is the speed of recompilation when adding transient code like this.
(Simple) facilities I'd like to see to help with debugging include:
- A common version of Debug.Trace that is part of the standard prelude
(i.e. no additional imports needed).
- A common version of assert that is part of the standard prelude, which
implementations are free to elide.
- "Remote observation": a system like Hugs might provide a command option
(i.e. without code modification) to observe evaluation of a named function
in evaluation of a larger program. Using the "observe" functions I found
to be rather awkward because if too many observations are coded they get
mixed up, so I don't use them at all. But if a simple top-level command
could observe named functions then it would be easy to use more selectively.
Also, a convention for embedding test cases in module code which can be
checked by the compiler is a possibility that I think was mentioned here
some time ago.
#g
------------
Graham Klyne
For email:
http://www.ninebynine.org/#Contact
The solution you describe forces you to put code
in functions to handle cases that are outside its
domain. Usually I just want to know what function
are calling with the pathological input.
e.g.
quadratic a b c = read $ show ((-b + root)/2/a),(-b - root)/2/a)
where root = sqrt(b*b - 4*a*c)
I want to know which function is passing in things
to make the root imaginary or a==0. I don't want
to rewrite this function so it handles these
exceptions explicitly.
-Alex-
_________________________________________________________________
S. Alexander Jacobson mailto:m...@alexjacobson.com
tel:917-770-6565 http://alexjacobson.com
Thanks! That is a useful tip.
--
Fergus J. Henderson | "I have always known that the pursuit
Galois Connections, Inc. | of excellence is a lethal habit"
Phone: +1 503 626 6616 | -- the last words of T. S. Garp.