ScalaCheck 产生 StackOverflowError

ScalaCheck generates StackOverflowError

我想创建生成器(用于 ScalaCheck)来生成命题公式。如果我创建一个生成器来生成带有变量和逻辑运算符(例如:A 和 B)的公式,一切都是正确的。但是如果我添加或,暗示而不是,ScalaCheck 生成 Exception: java.lang.WhosebugError.

import org.scalacheck.Prop
import org.scalacheck.Properties
import org.scalacheck.Gen

object FormulaWffSpecification extends Properties("FormulaWff") {

  abstract class FormulaWff {
    def size: Int
    def depths: Int
  }
  case class And(left: FormulaWff, right: FormulaWff)     extends FormulaWff
  case class Or(left: FormulaWff, right: FormulaWff)      extends FormulaWff
  case class Implies(left: FormulaWff, right: FormulaWff) extends FormulaWff
  case class Not(son: FormulaWff)                         extends FormulaWff
  case class Var(string: String)                          extends FormulaWff

  val genAnd = for {
    left  <- myGenFormula
    right <- myGenFormula
  } yield And(left, right)

  val genOr = for {
    left  <- myGenFormula
    right <- myGenFormula
  } yield Or(left, right)

  val genImplies = for {
    left  <- myGenFormula
    right <- myGenFormula
  } yield Implies(left, right)

  val genNot = for {
    son <- myGenFormula
  } yield Not(son)

  val genVar = Gen.oneOf(Var("A"),Var("B"))

  def myGenFormula: Gen[FormulaWff] =
    Gen.lzy(Gen.oneOf(genVar, genAnd, genImplies, genOr, genNot))

  property("size(t) <= 2^(depths(t) + 1) - 1") = {
    Prop.forAll(myGenFormula) { f: FormulaWff =>
      f.size <= Math.pow(2, f.depths + 1) - 1
    }
  }
}

显而易见的直觉是 myGenFormula 的定义是递归的。这就是堆栈溢出的解释。

解决这个问题的一部分是确保在 myGenFormula 的正确位置添加 Gen.lzy。这确保执行生成器的单一路径并避免不必要地执行所有递归生成器。

myGenFormula 的定义存在一个次要问题,会导致堆栈溢出。如所写,myGenFormula 的定义在统计上不太可能终止。 genVar 生成器是终止生成器,但它与其他非终止生成器的权重相同。没有什么可以阻止 ScalaCheck 在达到堆栈溢出之前生成无限深度的数据结构。

有两种方法可以帮助递归生成器在 ScalaCheck 中终止。您可以将带有 Gen.sized 的数字深度参数传递给您的生成器,或者您可以使用 Gen.frequency.

相互递归生成器也存在初始化问题。您需要在引用 myGenFormula 的生成器 val 上使用 lazy 关键字来避免这种情况。

这是一个在您的代码中添加 lazyGen.lzyGen.frequency 的解决方案,以便它运行和终止。您可能需要根据您的测试需求进行调整。

lazy val genAnd = for {
  left <- myGenFormula
  right <- myGenFormula
} yield And(left, right)

lazy val genOr = for {
  left <- myGenFormula
  right <- myGenFormula
} yield Or(left, right)

lazy val genImplies = for {
  left <- myGenFormula
  right <- myGenFormula
} yield Implies(left, right)

lazy val genNot = for {
  son <- myGenFormula
} yield Not(son)

val genVar =
  Gen.oneOf(Var("A"), Var("B"))

val myGenFormula: Gen[FormulaWff] =
  Gen.frequency(
    4 -> genVar,
    1 -> Gen.lzy(genAnd),
    1 -> Gen.lzy(genImplies),
    1 -> Gen.lzy(genOr),
    1 -> Gen.lzy(genNot))

property("myGenFormula") = {
  Prop.forAll(myGenFormula) { f: FormulaWff =>
    true
  }
}