Question

The GHC.Exts package exports breakpoint and breakpointCond. Does anyone know how to use these functions?

From their names I guess they would allow me to set up permanent GHCi breakpoints, but when I add them to my program nothing happens. For example, no breakpoints are triggered when I load this program into GHCi and run it with main, :main, or :trace main:

import GHC.Exts

idNat x = breakpointCond (x > 0) x

main = do
  putStrLn "Starting main"
  putStrLn . show $ idNat 3
  putStrLn $ breakpoint "Ending main"

Note: I know how to set breakpoints in GHCi using :break and I'm using GHC 7.6.3.

Was it helpful?

Solution

Looking at the commit history, it seems like this feature used to work at some point. However, apparently it got removed (either accidentally or on purpose) when the implementation of breakpoints was reworked (grep for breakpointName). I've filed a ticket about this.

OTHER TIPS

Here's a summary of some workarounds, none very satisfactory.

An obvious workaround that doesn't actually work

  1. Put this Breakpoint module in your source tree:

    module Breakpoint where
    
    breakpoint :: a -> a
    breakpoint x = x
    
    breakpointCond :: Bool -> a -> a
    breakpointCond True  x = x
    breakpointCond False x = x
    
  2. Import Breakpoint instead of GHC.Exts. (We can only set breakpoints on interpreted code, so we can't simply set break points on the GHC.Exts break point functions.)

  3. Load your code in GHCi and set appropriate break points in the Breakpoint module:

    ghci> :load <your main module>
    ghci> :break Breakpoint 4
    ghci> :break Breakpoint 7
    

    Note that the second break point is on the True branch of breakpointCond.

  4. Trace your code:

    ghci> :trace main
    

Problems with obvious approach

The problem with this approach is that you get a bunch of break points in the Breakpoint module, but when you reach them your actual computation (what you really care about) is usually not in the history trace. I don't understand why this is, but here's an example to illustrate it:

module Eg2 where
import Breakpoint

fib 0 = 0
fib 1 = 1
fib n = breakpoint $ fib (n-1) + fib (n-2)

Then:

ghci> :load Eg2.hs
ghci> :break Breakpoint 4

Now, note that fib 3 = fib 2 + fib 1 = (fib 1 + fib 0) + fib 1, so that we fib 3 should result in us hitting two break points, once at fib 3 and once at fib 2. However:

ghci> :trace fib 3
Stopped at Breakpoint.hs:4:5-20
_result :: a = _
[Breakpoint.hs:4:5-20] *Eg2
ghci> :history 
-1  : fib (Eg2.hs:6:13-46)
-2  : fib (Eg2.hs:(4,5)-(6,46))
<end of history>
[Breakpoint.hs:4:5-20] *Eg2
ghci> :back 
Logged breakpoint at Eg2.hs:6:13-46
_result :: a1
n :: Integer
[-1: Eg2.hs:6:13-46] *Eg2
ghci> n
3

So, yes, we hit a break point at fib 3. But then:

[-1: Eg2.hs:6:13-46] *Eg2
ghci> :continue 
Stopped at Breakpoint.hs:4:5-20
_result :: a = _
[Breakpoint.hs:4:5-20] *Eg2
ghci> :history 
-1  : breakpoint (Breakpoint.hs:4:5-20)
-2  : fib (Eg2.hs:6:13-46)
-3  : fib (Eg2.hs:(4,5)-(6,46))
<end of history>
[Breakpoint.hs:4:5-20] *Eg2
ghci> :back 
Logged breakpoint at Breakpoint.hs:4:5-20
_result :: a
[-1: Breakpoint.hs:4:5-20] *Eg2
ghci> :back 
Logged breakpoint at Eg2.hs:6:13-46
_result :: a1
n :: Integer
[-2: Eg2.hs:6:13-46] *Eg2
ghci> n
3

Only the fib 3 is on the stack? There is no fib 2 call, even though we're currently stopped at its breakpoint! Indeed, continuing finishes, returning fib 3 = 2:

[-2: Eg2.hs:6:13-46] *Eg2
ghci> :continue 
2

Improvement

It's not hard to make the context of the breakpoint call available at the break point. Although this doesn't solve the issue of the history trace not telling you how you got to the break point, it does let you inspect the context interactively (so better than "printf debugging" with Debug.Trace).

Add a context argument to the break point functions:

module Breakpoint2 where

breakpoint :: b -> a -> a
breakpoint y x =
  const x y

breakpointCond :: Bool -> b -> a -> a
breakpointCond True  y x =
  const x y
breakpointCond False _ x = x

Because GHCi only shows the free variables when you stop at a break point, you can't go with a simpler definition like

breakpoint y x = x

Now consider our example again, but this time where we manually pass the context we care about to breakpoint:

module Eg3 where
import Breakpoint2

fib 0 = 0
fib 1 = 1
fib n = breakpoint ("n",n) $ fib (n-1) + fib (n-2)

No we can inspect n:

ghci> :load Eg3.hs 
ghci> :break Breakpoint2 5

See n = 3 on the first call:

ghci> :trace fib 3
Stopped at Breakpoint2.hs:5:7-15
_result :: a = _
x :: a = _
y :: ([Char], Integer) = ("n",3)
[Breakpoint2.hs:5:7-15] *Eg3
ghci> :continue

See n = 2 on the second call:

Stopped at Breakpoint2.hs:5:7-15
_result :: a = _
x :: a = _
y :: ([Char], Integer) = ("n",2)

Interactively compute using the context:

[Breakpoint2.hs:5:7-15] *Eg3
ghci> let (_,n) = y
[Breakpoint2.hs:5:7-15] *Eg3
ghci> n * n
4

However, the trace history is still fairly useless, including the breakpoint calls, but fib calls in which they occur.

Comparison with setting a break point on directly on your code

The goal of the breakpoint function is to allow you easily set persistent break points in your code, versus using a bunch of :break <some line> <your module> statements which become invalid each time your line numbers change. However, manually setting breakpoints does yield much better history traces. For example:

module Eg4 where

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

Set breakpoint directly:

ghci> :load Eg4.hs 
ghci> :break Eg4 5 13

We need to column number (13) to make the breakpoint on the RHS of fib, instead of on the whole definition. Now we get proper traces:

ghci> :trace fib 4
Stopped at Eg4.hs:5:13-33
_result :: a1 = _
n :: Integer = 4
[Eg4.hs:5:13-33] *Eg4
ghci> :continue 
Stopped at Eg4.hs:5:13-33
_result :: a1 = _
n :: Integer = 3
[Eg4.hs:5:13-33] *Eg4
ghci> :continue 
Stopped at Eg4.hs:5:13-33
_result :: a1 = _
n :: Integer = 2
[Eg4.hs:5:13-33] *Eg4
ghci> :continue 
Stopped at Eg4.hs:5:13-33
_result :: a1 = _
n :: Integer = 2
[Eg4.hs:5:13-33] *Eg4
ghci> :back
Logged breakpoint at Eg4.hs:5:13-33
_result :: a1
n :: Integer
[-1: Eg4.hs:5:13-33] *Eg4
ghci> n
2
[-1: Eg4.hs:5:13-33] *Eg4
ghci> :back 
Logged breakpoint at Eg4.hs:5:13-33
_result :: a1
n :: Integer
[-2: Eg4.hs:5:13-33] *Eg4
ghci> n
3
[-2: Eg4.hs:5:13-33] *Eg4
ghci> :back 
Logged breakpoint at Eg4.hs:5:13-33
_result :: a1
n :: Integer
[-3: Eg4.hs:5:13-33] *Eg4
ghci> n
4
[-3: Eg4.hs:5:13-33] *Eg4
ghci> :history 
-1  : fib (Eg4.hs:5:13-33)
-2  : fib (Eg4.hs:5:13-33)
-3  : fib (Eg4.hs:5:13-33)
-4  : fib (Eg4.hs:(3,5)-(5,33))
<end of history>

Final note on breakpointCond

The breakpointCond seems even more useful, since GHCi doesn't have conditional breakpoints, but these can be affected by editing your code to include pointless branching on break point conditions. For example, suppose we only want to break when n is even:

module Eg5 where

fib 0 = 0
fib 1 = 1
fib n = case even n of
          True  -> fib (n-1) + fib (n-2)
          False -> fib (n-1) + fib (n-2)

Now, breaking on line 6, we only stop on even n. Of course, this transformation is a little annoying, e.g. we can't instead do

fib n = case even n of
          True  -> r
          False -> r
  where r = fib (n-1) + fib (n-2)

since then n is not free in r, and we can't use an "if" statement, since GHCi won't let you set a break point inside one.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top