Search code examples
haskellghci

GHCi Debugger not hitting breakpoints recursively: Why, and what's the solution?


I have a simple recursive program that doesn't quite work. So I'm trying to use the ghci debugger to figure out what's going on. I set breakpoints on all lines of functions recurse, progress and shaded, and it catches the first few. But when I get to the second line of progress, every invocation of :continue drops me back on that same line of code, even though I'm calling recurse and shaded from there and expecting my breakpoints to work. Here's the code:

import System.Environment

type Pos = (Int,Int) 
type Acc = ([[Pos]], [Pos])

main = do
  getArgs >>= putStrLn . show . length . combos . read . head

combos n = recurse n [] (allPos n) []

recurse :: Int -> [[Pos]] -> [Pos] -> [Pos] -> [[Pos]]
recurse n done avail inProg
   | length inProg == n = inProg:done
   | null avail = done
   | otherwise = fst $ foldr (progress n inProg) (done,avail) avail

progress :: Int -> [Pos] -> Pos -> Acc -> Acc
progress n inProg pos (done, avail)  =
  (recurse n done (filter (not . shaded pos) remain) (pos:inProg), remain)
  where remain = tail avail

allPos n =  [ (i,j) | i <- [0..n-1], j <- [0..n-1] ]

shaded :: Pos -> Pos -> Bool
shaded (i,j) (k,l) =
  k == i
  || l == j
  || k+l == i+j
  || k-l == i-j
  || abs (k-i) < 3 && abs (l-j) < 3

Why will the ghci debugger not stop on breakpoints in functions called by progress? Is there something like "non-reentrancy" turning them off? How can I get the debugger to break on every recursive call to these functions?

GHCi version 7.8.4

Update: I suspect this may have something to do with cached results, but it's not obvious to me that these functions have been called with the same arguments twice. Could be a bug in my code?


Solution

  • I think it is working as expected, but you have to take into account lazy evaluation.

    If you break at the guard lines in recurse (lines 13 -- 15) and the call to recurse in progress (line 19) you'll see this pattern when the program is run with :main 2:

    Stopped at prog0.hs:13:6-23   - recurse
    Stopped at prog0.hs:14:6-15   - recurse
    Stopped at prog0.hs:15:18-67  - recurse, call to foldr progress
    Stopped at prog0.hs:19:3-74   - in progress, pos = (1,1)
    Stopped at prog0.hs:19:3-74   - in progress, pos = (1,0)
    Stopped at prog0.hs:19:3-74   - in progress, pos = (0,1)
    Stopped at prog0.hs:19:3-74   - in progress, pos = (0,0)
    Stopped at prog0.hs:13:6-23   - in recurse
    Stopped at prog0.hs:14:6-15
    Stopped at prog0.hs:13:6-23   - in recurse
    Stopped at prog0.hs:14:6-15   
    Stopped at prog0.hs:13:6-23   - in recurse
    Stopped at prog0.hs:14:6-15  
    Stopped at prog0.hs:13:6-23   - in recurse
    Stopped at prog0.hs:14:6-15
    

    However, if you change progress to force its result:

    import Control.DeepSeq
    
    progress n inProg pos (done, avail)  = let
      result = (recurse n done (filter (not . shaded pos) remain) (pos:inProg), remain)
      in deepseq result result
      where remain = tail avail
    

    then the break point pattern is:

    Stopped at prog1.hs:14:6-23   - recurse
    Stopped at prog1.hs:15:6-15   - recurse
    Stopped at prog1.hs:16:18-67  - recurse, call to foldr progress
    Stopped at prog1.hs:21:6-26   - progress, pos = (1,1)
    Stopped at prog1.hs:14:6-23   - recurse
    Stopped at prog1.hs:15:6-15
    Stopped at prog1.hs:21:6-26   - progress, pos = (1,0)
    Stopped at prog1.hs:14:6-23   - recurse
    Stopped at prog1.hs:15:6-15
    Stopped at prog1.hs:21:6-26   - progress, pos = (0,1)
    Stopped at prog1.hs:14:6-23   - recurse
    Stopped at prog1.hs:15:6-15
    Stopped at prog1.hs:21:6-26   - progress, pos = (0,0)
    Stopped at prog1.hs:14:6-23   - recurse
    Stopped at prog1.hs:15:6-15
    

    The recursive calls to recurse are now interleaved because we are forcing them right away.