以下是一些解决方法的摘要,没有一个非常令人满意。
一个明显的解决方法,实际上不起作用
将此Breakpoint
模块放在您的源代码树中:
module Breakpoint where
breakpoint :: a -> a
breakpoint x = x
breakpointCond :: Bool -> a -> a
breakpointCond True x = x
breakpointCond False x = x
导入Breakpoint
而不是GHC.Exts
. (我们只能在解释代码上设置断点,所以我们不能简单地在GHC.Exts
断点函数上设置断点。)
Breakpoint
在 GHCi 中加载您的代码并在模块中设置适当的断点:
ghci> :load <your main module>
ghci> :break Breakpoint 4
ghci> :break Breakpoint 7
请注意,第二个断点位于 的True
分支上breakpointCond
。
跟踪您的代码:
ghci> :trace main
明显方法的问题
这种方法的问题是您在模块中获得了一堆断点Breakpoint
,但是当您到达它们时,您的实际计算(您真正关心的)通常不在历史跟踪中。我不明白为什么会这样,但这里有一个例子来说明它:
module Eg2 where
import Breakpoint
fib 0 = 0
fib 1 = 1
fib n = breakpoint $ fib (n-1) + fib (n-2)
然后:
ghci> :load Eg2.hs
ghci> :break Breakpoint 4
现在,请注意fib 3 = fib 2 + fib 1 = (fib 1 + fib 0) + fib 1
,这样我们fib 3
应该会遇到两个断点,一次是一次,fib 3
一次是fib 2
。然而:
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
所以,是的,我们在fib 3
. 但是之后:
[-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
只有fib 3
在堆栈上?没有fib 2
调用,即使我们当前在断点处停止!确实,继续完成,返回fib 3 = 2
:
[-2: Eg2.hs:6:13-46] *Eg2
ghci> :continue
2
改进
breakpoint
在断点处使调用的上下文可用并不难。尽管这并不能解决历史跟踪没有告诉您如何到达断点的问题,但它确实可以让您以交互方式检查上下文(比“printf 调试”更好Debug.Trace
)。
向断点函数添加上下文参数:
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
因为 GHCi 仅在您在断点处停止时显示自由变量,所以您不能使用更简单的定义,例如
breakpoint y x = x
现在再次考虑我们的示例,但这次我们手动将我们关心的上下文传递给breakpoint
:
module Eg3 where
import Breakpoint2
fib 0 = 0
fib 1 = 1
fib n = breakpoint ("n",n) $ fib (n-1) + fib (n-2)
不,我们可以检查n
:
ghci> :load Eg3.hs
ghci> :break Breakpoint2 5
n = 3
在第一次通话时查看:
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
请参阅n = 2
第二次通话:
Stopped at Breakpoint2.hs:5:7-15
_result :: a = _
x :: a = _
y :: ([Char], Integer) = ("n",2)
使用上下文交互式计算:
[Breakpoint2.hs:5:7-15] *Eg3
ghci> let (_,n) = y
[Breakpoint2.hs:5:7-15] *Eg3
ghci> n * n
4
但是,跟踪历史记录仍然相当无用,包括
breakpoint
调用,但fib
它们发生的调用。
与直接在代码上设置断点的比较
该breakpoint
函数的目标是让您可以轻松地在代码中设置持久断点,而不是使用:break <some line> <your module>
每次行号更改时都变得无效的一堆语句。但是,手动设置断点确实会产生更好的历史跟踪。例如:
module Eg4 where
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
直接设置断点:
ghci> :load Eg4.hs
ghci> :break Eg4 5 13
我们需要在列号 (13) 上创建断点
fib
,而不是在整个定义上。现在我们得到正确的痕迹:
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>
最后说明breakpointCond
这breakpointCond
似乎更有用,因为 GHCi 没有条件断点,但是这些可以通过编辑代码来包括断点条件上的无意义分支来影响。例如,假设我们只想在n
偶数时中断:
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)
现在,在第 6 行中断,我们只停在 even 上n
。当然,这种转换有点烦人,例如我们不能代替
fib n = case even n of
True -> r
False -> r
where r = fib (n-1) + fib (n-2)
因为 thenn
在 中不是免费的r
,我们不能使用“if”语句,因为 GHCi 不会让你在其中设置断点。