GHCi 调试器没有递归地命中断点:为什么,解决方案是什么?

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

我有一个简单的递归程序,但效果不佳。所以我正在尝试使用 ghci 调试器来弄清楚发生了什么。我在函数 recurseprogressshaded 的所有行上都设置了断点,它捕获了前几个。但是当我到达 progress 的第二行时,每次调用 :continue 都会让我回到同一行代码,即使我从那里调用 recurseshaded 并且期待我的断点起作用。这是代码:

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

为什么 ghci 调试器不会在 progress 调用的函数中的断点处停止?是否有 "non-reentrancy" 之类的东西可以关闭它们?我怎样才能让调试器在每次递归调用这些函数时中断?

GHCi 版本 7.8.4

更新: 我怀疑这可能与缓存结果有关,但对我来说这些函数已经用相同的参数调用了两次并不明显。可能是我的代码中的错误?

我认为它按预期工作,但你必须考虑惰性评估。

如果您在 recurse(第 13 -- 15 行)中的警戒线上中断并且正在调用递归(第 19 行),您将在程序 运行 与 :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

但是,如果您更改 progress 以强制其结果:

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

则断点模式为:

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

recurse 的递归调用现在是交错的,因为我们立即强制执行它们。

我正在添加另一个答案来演示如何使用 Debug.Trace 执行 "printf" 纯函数调试。

这里是 recurseprogress 添加了跟踪语句打印 他们的通话参数。我们可以在这里安全地使用 undefined 因为 跟踪调用将始终 return False。

import System.Environment
import Debug.Trace

...

recurse :: Int -> [[Pos]] -> [Pos] -> [Pos] -> [[Pos]]
recurse n done avail inProg
   | trace msg False = undefined
   | length inProg == n = inProg:done
   | null avail = done
   | otherwise = fst $ foldr (progress n inProg) (done,avail) avail
   where msg = unwords ["recurse:", show n, show done, show avail, show inProg]

progress :: Int -> [Pos] -> Pos -> Acc -> Acc
progress n inProg pos (done, avail)
  | trace msg False = undefined
    where msg = unwords ["progress:", show n, show inProg, show pos, show (done, avail)]
progress n inProg pos (done, avail)  =
  (recurse n done (filter (not . shaded pos) remain) (pos:inProg), remain)
  where remain = tail avail

...

这里是 :main 2 的输出:

recurse: 2 [] [(0,0),(0,1),(1,0),(1,1)] []
progress: 2 [] (1,1) ([],[(0,0),(0,1),(1,0),(1,1)])
recurse: 2 [] [] [(1,1)]
progress: 2 [] (1,0) ([],[(0,1),(1,0),(1,1)])
recurse: 2 [] [] [(1,0)]
progress: 2 [] (0,1) ([],[(1,0),(1,1)])
recurse: 2 [] [] [(0,1)]
progress: 2 [] (0,0) ([],[(1,1)])
recurse: 2 [] [] [(0,0)]

一般情况下,添加跟踪语句的评估会以严格的方式进行,因为打印出函数参数会强制执行它们。