3

GHC.Exts导出breakpointbreakpointCond. 有谁知道如何使用这些功能?

从他们的名字我猜他们会允许我设置永久的 GHCi 断点,但是当我将它们添加到我的程序中时,什么也没有发生。例如,当我将此程序加载到 GHCi 并使用 、 或 运行它时,不会main触发:main断点:trace main

import GHC.Exts

idNat x = breakpointCond (x > 0) x

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

注意:我知道如何在 GHCi 中设置断点,:break并且我正在使用 GHC 7.6.3。

4

2 回答 2

2

查看提交历史,似乎此功能曾经在某个时候起作用。然而,很明显,当断点的实现被重新设计(grep for )时,它被删除了(无论是意外还是故意breakpointName)。我已经为此提交了一张罚单

于 2013-10-10T08:28:18.477 回答
1

以下是一些解决方法的摘要,没有一个非常令人满意。

一个明显的解决方法,实际上不起作用

  1. 将此Breakpoint模块放在您的源代码树中:

    module Breakpoint where
    
    breakpoint :: a -> a
    breakpoint x = x
    
    breakpointCond :: Bool -> a -> a
    breakpointCond True  x = x
    breakpointCond False x = x
    
  2. 导入Breakpoint而不是GHC.Exts. (我们只能在解释代码上设置断点,所以我们不能简单地在GHC.Exts断点函数上设置断点。)

  3. Breakpoint在 GHCi 中加载您的代码并在模块中设置适当的断点:

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

    请注意,第二个断点位于 的True分支上breakpointCond

  4. 跟踪您的代码:

    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 不会让你在其中设置断点。

于 2013-10-10T19:01:42.503 回答