找出"比率有零分母"的例外情况

gss*_*der 5 debugging haskell

作为学习Haskell过程中的个人练习,我试图将这个F#片段移植到随机艺术中.

我没有嵌入完整的源代码,没有膨胀这个问题,但可以作为要点.

该计划的一个重要部分是这种Expr类型:


data Expr =
    VariableX
  | VariableY
  | Constant
  | Sum Expr Expr
  | Product Expr Expr
  | Mod Expr Expr
  | Well Expr
  | Tent Expr
  | Sin Expr
  | Level Expr Expr Expr
  | Mix Expr Expr Expr
  deriving Show
Run Code Online (Sandbox Code Playgroud)

和两个功能:

  • gen :: Int -> IO Expr在给定多次迭代的情况下,随机生成树状结构

  • eval :: Expr -> IO (Point -> Rgb Double) 遍历树并终止生成绘图功能.

传递给gen 更高的数字是产生以下异常的概率:Ratio has zero denominator.

我是Haskell的新手,所以要解决我尝试编译的问题,如上所述:


ghc RandomArt.hs -prof -auto-all -caf-all
Run Code Online (Sandbox Code Playgroud)

只获得这个(对我来说没用)信息:


$ ./RandomArt +RTS -xc

*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace:
  GHC.Real.CAF
  --> evaluated by: Main.eval.\,
  called from Main.eval,
  called from Main.tga.pxs',
  called from Main.tga,
  called from Main.save,
  called from Main.main,
  called from :Main.CAF:main
  --> evaluated by: Main.eval.\.r,
  called from Main.eval.\,
  called from Main.eval,
  called from Main.tga.pxs',
  called from Main.tga,
  called from Main.save,
  called from Main.main,
  called from :Main.CAF:main
*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace:
  Main.tga,
  called from Main.save,
  called from Main.main,
  called from GHC.Real.CAF
RandomArt: Ratio has zero denominator
Run Code Online (Sandbox Code Playgroud)

将生成的函数持久保存到TGA文件的代码可以正常工作,因为它是我之前的excercize(来自OCaml的端口).

我尝试ExprGHCi执行各种树,手工组装数据或在程序中应用函数但我无法识别错误.

Haskell文档讨论了一个名为loch应该能够编译保留源代码行号的包,但我无法安装它(我通常安装cabal install我需要的每个包).

问题,说实话是两个:

  • 哪个是bug(在这种特定情况下)?

  • 我需要掌握哪个工具才能找到这样的bug(或者一般的bug)?

提前致谢.

Zet*_*eta 6

例外

让我们首先关注异常.

找到错误

哪个是bug(在这种特定情况下)?

mod'.如果我们提供替代版本而不是一个版本,我们可以轻松地检查这个Data.Fixed:

mod' :: RealFrac a => a -> a -> a
mod' _ 0 = error "Used mod' on 0"
mod' a b =
    let k = floor $ a / b
    in a - (fromInteger k) * b
Run Code Online (Sandbox Code Playgroud)

我们现在得到 Used mod' on 0.

合理

我需要掌握哪个工具才能找到这样的bug(或者一般的bug)?

在这种情况下,必要的提示已经在异常的消息中:

Ratio has zero denominator
Run Code Online (Sandbox Code Playgroud)

这意味着在a的上下文中有一个除以零的地方Ratio.所以你需要照顾你划分东西的所有地方.因为你只使用(/)mod',归结为它们中的一个是否真的可以抛出这个异常:

  • (/)±Infinity如果使用Double,通常会按除以零返回,
  • mod'toRational内部使用,这是一个Ratio Integer.

所以只有一个罪魁祸首.请注意,如果b不为零,则其他实现会产生相同的结果.

实际问题

使用modmod'使用b == 0没有明确定义.毕竟,模运算应该包含以下属性:

prop_mod :: Integral n => n -> n -> Bool
prop_mod a b = 
  let m = a `mod` b
      d = a `div` b

  in a == b * d + m   -- (1)
     && abs m < abs b -- (2)
Run Code Online (Sandbox Code Playgroud)

如果b == 0,则不存在任何对(d, m)(1)(2)成立的对.如果我们放松这个法律并扔掉(2),结果mod不再是唯一的.这导致以下定义:

mod' :: RealFrac a => a -> a -> a
mod' a 0 = a -- this is arbitrary
mod' a b =
    let k = floor $ a / b
    in a - (fromInteger k) * b
Run Code Online (Sandbox Code Playgroud)

但是,这是一个任意的定义.你必须问自己,"如果我不能mod以理智的方式使用,我真正想做什么".由于F#显然没有抱怨a % 0,看看他们的文档.

无论哪种方式,您都不能使用库mod函数,因为它们没有为零分母定义.

  • 小警告:如果你在`Rational`而不是浮点使用它,`(/ 0)`完全能够引发错误.虽然在这种情况下,OP似乎没有使用`Rational`,只是通过`mod'`间接使用. (3认同)