GHCi debugger status

Claus Reinke claus.reinke at talk21.com
Mon Nov 24 07:50:20 EST 2008


>> - when I'm at a break point, I'd really like to see the current scope
>>    or, if that is too expensive, the next enclosing scope, in full
>>    (not only would that tell me what instantiation of my code I'm in,
>>    it would also seem necessary if I want to reconstruct what the
>>    current expression is)
> 
> I don't understand what you mean here - surely in order to "reconstruct 
> what the current expression is" you only need to know the values of the 
> free variables of that expression?  Also I don't understand what you mean 
> by the "next enclosing scope".  Could you give an example?

If <expr> is the current expression

    f a b = <expr> -- 'a' and 'b' should be inspectable, independent of <expr>

    g x y = <expr> where a = .. -- 'a' should be inspectable (ideally, 'x'/'y', too)

> I don't know what "partially defined value form" is.  The debugger shows 
> the source code as... source code!

    :list shows source code (without substitution)
    :print shows partially defined values (partially defined value form, with thunks)
    :force tries to evaluate, replacing uninspectable thunks with values

There is no way to see source with substituted variables, or unevaluated
expressions. So there's an information gap between source and values.
It is in this gap that many of the interesting/buggy reductions happen
(at least in my code;-).

> I think I know what you mean though.  You want the debugger to substitute 
> the values of free variables in the source code, do some reductions, and 
> show you the result.  Unfortunately this is quite hard to implement in GHCi 
>  (but I agree it might be useful), because GHCi is not a source-level 
> interpreter.

Yes, that is what I've been missing ever since I moved from my previous
functional language to Haskell. But I agree that it would be hard to add
as an afterthought. I have high hopes that the GHCi debugger + scope
might go most of the way in terms of practical needs.

I really think the GHCi debugger is a great step in the right direction,
but without being able to inspect the context of the current expression
(about which we can only inspect its value, evaluation status, and free
variables) it is stuck short of fullfiling its potential. Unless I'm using it
wrongly, which is why I was asking.

>> Currently, I only use the debugger rarely, and almost always have to 
>> switch to trace/etc to pin down what it is hinting at. What is the 
>> intended usage pattern that makes the debugger more effective/
>> convenient than trace/etc, and usable without resorting to the latter?
> 
> Set a breakpoint on an expression that has the variable you're interested 
> in free, and display its value.  If your variable isn't free in the 
> expression you want to set a breakpoint on, then you can add a dummy 
> reference to it.

Adding dummy references means modifying the source, at which point
trace variants do nearly as well. The remaining value of the debugger is
that I can decide at debug time whether or not I want to see the traced
value. Without modifying the source code, there may be no way to step
forward to any expression that has the interesting variables free.

In small examples, it is usually easy to work around the debugger limitations,
so I'll have to remember to make a note next time I encounter the issues
in real life debugging. However, even in small examples, one can see hints
of the issues. Consider this code and session:

    f x y z | x<y       = z
        | otherwise = z*y

---------------------
$ /cygdrive/d/fptools/ghc/ghc/stage2-inplace/ghc.exe --interactive Debug.hs  -ignore-dot-ghci
GHCi, version 6.11.20081122: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
[1 of 1] Compiling Main             ( Debug.hs, interpreted )
Ok, modules loaded: Main.
*Main> :break f
Breakpoint 0 activated at Debug.hs:(1,0)-(2,24)
*Main> f 3 2 1
Stopped at Debug.hs:(1,0)-(2,24)
_result :: a = _
[Debug.hs:(1,0)-(2,24)] *Main> :list
   vv
1  f x y z | x<y       = z
2          | otherwise = z*y
                            ^^
3
[Debug.hs:(1,0)-(2,24)] *Main> :step
Stopped at Debug.hs:1:10-12
_result :: a = _
[Debug.hs:1:10-12] *Main> :list
1  f x y z | x<y       = z
             ^^^
2          | otherwise = z*y
[Debug.hs:1:10-12] *Main> :step
Stopped at Debug.hs:2:10-18
_result :: a = _
[Debug.hs:2:10-18] *Main> :list
1  f x y z | x<y       = z
2          | otherwise = z*y
             ^^^^^^^^^
3
[Debug.hs:2:10-18] *Main> :step
Stopped at Debug.hs:2:22-24
_result :: Integer = _
y :: Integer = 2
z :: Integer = _
[Debug.hs:2:22-24] *Main> :list
1  f x y z | x<y       = z
2          | otherwise = z*y
                         ^^^
3
[Debug.hs:2:22-24] *Main> :step
2
---------------------

Things to note:

- when reaching the breakpoint "in" 'f', one isn't actually in 'f' yet - nothing
    about 'f' can be inspected
- at no point in the session was 'x' inspectable, even though it is likely to
    contain information needed to understand 'f', especially when we are 
    deep in a recursion of a function that can be called from many places;
    this information doesn't happen to be needed in the current branch, but 
    debugging the current expression always happens in a context, and 
    accessing information about this context is what the GHCi debugger 
    doesn't seem to support well

In this particular example, the second item is most likely a bug (the free 
variables of the guard were never offered for inspection). That is perhaps 
more easily shown in the other branch:

---------------------
*Main> f 1 2 3
Stopped at Debug.hs:(1,0)-(2,24)
_result :: a = _
[Debug.hs:(1,0)-(2,24)] *Main> :list
   vv
1  f x y z | x<y       = z
2          | otherwise = z*y
                            ^^
3
[Debug.hs:(1,0)-(2,24)] *Main> :step
Stopped at Debug.hs:1:10-12
_result :: a = _
[Debug.hs:1:10-12] *Main> :list
1  f x y z | x<y       = z
             ^^^
2          | otherwise = z*y
[Debug.hs:1:10-12] *Main> :step
Stopped at Debug.hs:1:22
_result :: a = _
z :: a = _
[Debug.hs:1:22] *Main> :list
1  f x y z | x<y       = z
                         ^
2          | otherwise = z*y
[Debug.hs:1:22] *Main> :step
3
---------------------

Cheers,
Claus



More information about the Glasgow-haskell-users mailing list