Debugging 用GHCi调试Haskell程序中的无限循环

Debugging 用GHCi调试Haskell程序中的无限循环,debugging,haskell,infinite-loop,ghc,ghci,Debugging,Haskell,Infinite Loop,Ghc,Ghci,这是我第一次在正在编写的Haskell程序中遇到无限循环。我已经把它缩小到了一个相当具体的代码部分,但我似乎无法准确地指出我在哪里有一个非终止递归定义。我对GHCi中的:痕迹和:历史有点熟悉,但问题是,我的代码的一些分支涉及对Data.Map.Map的大量递归修改,因为Mapx是通过adjust根据x'根据另一个Map中的值调整Mapx'中的某些内容而获得的。这里的细节并不重要,但正如您可能知道的那样,如果这种情况以一种相互交织的递归方式发生,我的调用历史将完全陷入maplookups、adju

这是我第一次在正在编写的Haskell程序中遇到无限循环。我已经把它缩小到了一个相当具体的代码部分,但我似乎无法准确地指出我在哪里有一个非终止递归定义。我对GHCi中的:痕迹和:历史有点熟悉,但问题是,我的代码的一些分支涉及对
Data.Map.Map
的大量递归修改,因为Map
x
是通过
adjust
根据
x'
根据另一个Map中的值调整Map
x'
中的某些内容而获得的。这里的细节并不重要,但正如您可能知道的那样,如果这种情况以一种相互交织的递归方式发生,我的调用历史将完全陷入map
lookup
s、
adjust
ments和
insert
ions中涉及的各种比较中

有人能推荐一种更有效的方法来定位无限循环吗?例如,将调用历史记录限制为来自单个源文件的调用会有很大帮助。

正如ShiDoiSi所说,“通过眼睛”解决通常是最成功的方法

如果要在相同的函数中绑定到不同的同名变量x、x'等,可以尝试在文件顶部启用警告:

{-# OPTIONS -Wall #-} 

如果您绑定到错误的对象并进行失控递归,这可能会帮助您发现问题-例如,通过指示意外使用阴影。

确保您已充分使用GHCi调试器,包括设置-fbreak on exception(如果您得到
,是否有用?)并且确保你已经尝试了斯蒂芬关于使用GHC警告的建议


如果这些失败(GHCi调试器确实不应该“失败”,这只是解释数据的问题),那么尝试在循环情况下运行,以便您可以直观地看到未被评估的分支和值,如果它是循环的,那么应该完成的事情可能甚至没有被评估,并且将显示在标记的HTML中。

您不能使用:back和:forward来访问您的历史记录/跟踪,并找出调用之间映射的演变吗

您应该能够发现导致递归循环的模式


——如果它太复杂,你可能已经达到了编写一些代码太智能的程度,让你去调试(或者可能太复杂,你应该重构它^ ^)-< /P> < P>我在一个长的调试会话的中间寻找无限循环的原因。我离得很近了,这对我帮助最大。假设您的循环是由以下内容引起的:

...
x1 = f1 x2 y
x2 = f2 z x3
x3 = f3 y x1
...
所以x1依赖于x2,x2依赖于x3,x3依赖于x1。糟糕

f1、f2、f3定义中的Spready跟踪函数。比如:

f1 x y | trace ("f1: ") False = undefined
f1 x y = ... -- definition of f1

f2 x y | trace ("f2: ") False = undefined
f2 x y = ... -- definition of f2

-- same for f3
f3:
f2: x: x_value
f1: 
<<loop>>
运行程序以查看调用了哪些函数。输出可能类似于

f3:
f2:
f1: 
<<loop>>
然后,输出将如下所示:

f1 x y | trace ("f1: ") False = undefined
f1 x y = ... -- definition of f1

f2 x y | trace ("f2: ") False = undefined
f2 x y = ... -- definition of f2

-- same for f3
f3:
f2: x: x_value
f1: 
<<loop>>
然后输出将是

f3:
<<loop>>
f3:
因为f2的第二个参数由于循环依赖性而无法计算

现在您知道无限循环中的一个函数是f2,它的第二个参数(但不是第一个)具有循环依赖性


调试愉快

我很惊讶没有人提到所有Haskell性能问题得到的普遍响应(无限运行时是“性能问题”的一个极端例子):评测


我刚刚能够使用评测快速识别无限循环。为完整起见,请使用
-prof-fprof auto
编译程序,然后运行程序足够长的时间,以便在分析统计数据中显示出有问题的函数。例如,我希望我的程序在下载HPC的所有链接中都能完成。Eric:
cabal install HPC
“如何实际使用HPC。也许你可以解释一下如何使用HPC检测无限循环?”?我生成了一个标记报告,但其中没有任何内容会将我指向我的特定无限循环。@Eric这更适合于一个新问题,而不是一个注释。简而言之:
ghc-fhpc x.hs/x;hpc标记x;xdg open Main.hs.html
。HTML应该包含任何未被计算为黄色的内容。如果你有一系列相对线性的let语句(这个问题经常出现),你会看到类似于二进制操作的东西,它需要两个参数,但一个参数是黄色的,而另一个不是黄色的——另一个参数可能是循环的。这不是一个很好的调试方法,调试器是首选,但这是我听说过的人们使用的方法。如果
:history
只给出几个断点,或者根本没有断点,这就不起作用。非常感谢!我开始发疯了,终于能够通过这种方式找到问题。这是一个阴影,但更难发现,因为它以匹配的模式出现…当使用
堆栈ghci
时,可以使用
--profile
启用评测。Neil Mitchell建议在此处创建一个损坏的.hp文件:,即“删除最后一个END_示例之后的所有内容,以恢复hp2ps可以理解的配置文件。”在黑了Haskell的好几行代码之后,我不得不说,我在调试方面从来没有太多的运气。最终帮助我追踪问题的始终是对代码的彻底修改和重构。当然,这只是个轶事。如果你发布一些代码,社区可能会帮助你调试。@fuzzxl:是的,这是一个很好的调试策略。它帮助了我很多次。我已经能够通过在纸上计算我的表达式来解决我的特殊问题,直到我能够看到无限递归的定义。但是,我仍然想了解更多关于不同调试技术的信息,所以我将留下这个问题。
                                                 individual      inherited
COST CENTRE         MODULE     no.    entries   %time  %alloc   %time %alloc
...
primroot.\          Zq         764          3    10.3    13.8    99.5  100.0
 primroot.isGen     Zq         1080   50116042    5.3     6.9    89.2   86.2
  primroot.isGen.\  Zq         1087   50116042   43.4    51.7    83.8   79.3
   fromInteger      ZqBasic    1088          0   40.4    27.6    40.4   27.6