QuickCheck 生成器未终止

QuickCheck Generator not terminating

这个 QuickCheck 生成器

notEqual :: Gen (Int, Int)
notEqual = do
  (a, b) <- arbitrary
  if a /= b
    then return (a, b)
    else notEqual

传递给 forAll 时似乎没有终止。我猜递归会以某种方式进入无限循环,但为什么呢?

如果您对 termination/finding 结果有疑问,您可以随时尝试规避这一点:

notEqual' :: Gen (Int, Int)
notEqual' = do
  start <- arbitrary
  delta <- oneof [pos, neg]
  pure (start, start + delta)
  where
      pos = getPositive <$> arbitrary
      neg = getNegative <$> arbitrary

当然,PostitiveNegative 在内部都使用 suchThat,所以正如 Ashesh 提到的那样

notEqual :: Gen (Int, Int)
notEqual = genPair `suchThat` uncurry (/=)
  where genPair = arbitrary

可能更容易

@Ashesh 和@Carsten 指出的 suchThat 组合子绝对是我正在寻找的,简洁地和惯用地生成一个不相等的对。

无限递归的解释(感谢@oisdk):

所有 QuckCheck 运行程序(quickCheckforAll 等)通过 size parameter 来测试生成器。这没有定义语义,但早期测试使用一个小参数,从 0* 开始,逐渐增长。生成器使用它来生成不同 'sizes,' 的样本,无论这对特定数据类型可能意味着什么。

arbitrary 用于整数类型(由 arbitrary 递归调用 (Int, Int)),将其用于生成值的大小 - generate an integral between 0和 size.

这意味着,不幸的是,quickCheck 尝试的 第一次测试 (或者在我的情况下 forAll)使用大小 0,它只能生成(0, 0)。这总是无法通过/=的测试,导致动作无限递归,寻找更多。

* 我假设是这样,因为大小参数的行为似乎没有在任何地方记录。