Re: [Haskell] modern language design, stone age tools
On 23-Jun-2004, Hal Daume III <hdaume@ISI.EDU> wrote:
On Wed, 23 Jun 2004, Fergus Henderson wrote:
On 23-Jun-2004, MR K P SCHUPKE <k.schupke@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?
Wasn't addressed to me, but here's what I do:
write the following function:
head_ x [] = error ("head_: " ++ show x) head_ _ l = head l
and then replace each occurance of "head" with "head_ 1" or "head_ 2" etc., so I can know where it failed.
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.
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" Head would be defined as e.g. head [] = fail $ "empty list.\n" ++ ?failMsg head (x:xs) = x It would be even nicer if lineNumber was automatically packed in the failMsg so the output of myFunc [] would be Prelude.head: emptyList. [Line ???] MyModule.myFunc: "myfunc 0 caused the error" [Line 50] But, just being able to pass a msg would make debugging LOADS easier (eliminating much of the need for a debugger to supply a stack trace). Since implicit parameters are part of the type system, it would be even cooler to be able to identify all the functions in your code that may fail (i.e. that carry ?failMsg). You can then target your debugging on those functions. Does this make any sense? -Alex- * I've never actually used implicit parameters. I just swiped the syntax I saw skimming: http://www.cse.ogi.edu/~mbs/pub/implicit_parameters/ _________________________________________________________________ S. Alexander Jacobson mailto:me@alexjacobson.com tel:917-770-6565 http://alexjacobson.com On Wed, 23 Jun 2004, Fergus Henderson wrote:
On 23-Jun-2004, Hal Daume III <hdaume@ISI.EDU> wrote:
On Wed, 23 Jun 2004, Fergus Henderson wrote:
On 23-Jun-2004, MR K P SCHUPKE <k.schupke@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?
Wasn't addressed to me, but here's what I do:
write the following function:
head_ x [] = error ("head_: " ++ show x) head_ _ l = head l
and then replace each occurance of "head" with "head_ 1" or "head_ 2" etc., so I can know where it failed.
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. _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
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.] -- Alastair Reid
On Wed, Jun 23, 2004 at 09:43:46PM +0100, Alastair Reid wrote:
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.]
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⑈
participants (4)
-
Alastair Reid -
Fergus Henderson -
John Meacham -
S. Alexander Jacobson