在升级到scalacheck 1.13.3之后,我遇到了一个奇怪的问题,在那里派生A => B Or C的实例几乎总是会失败,而Or本质上是一个轻量级的Either。
这是我能写的重现这个问题的最简单的代码:
import org.scalatest.FunSuite
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalacheck.Shapeless._
class Testor extends FunSuite with GeneratorDrivenPropertyChecks {
sealed trait Or[+A, +B] extends Product with Serializable
case class Left[A](a: A) extends Or[A, Nothing]
case class Right[B](b: B) extends Or[Nothing, B]
test("reproduce") {
forAll { (i: Int, f: Int ⇒ Float Or Boolean) ⇒
f(i)
}
}
}此操作失败,出现以下错误:
RetrievalError was thrown during property evaluation.
Message: couldn't generate value
Occurred when passed generated values (
arg0 = 0, // 30 shrinks
arg1 = <function1>
)请注意,提供显式的Arbitrary[Float Or Boolean]解决了这个问题,因此很明显问题出在泛型派生中。
我不相信问题出在shapeless-scalacheck上--我试着写我自己的泛型派生,看看它是否有帮助,但以完全相同的方式失败了。
有些奇怪的事情,可能是由于任意函数的工作方式,函数实际上是生成的,但在计算时失败了。
感谢大家对此的帮助/建议,因为我有点被卡住了。
发布于 2016-11-04 07:16:22
这里的问题似乎是两面性的:
org.scalacheck.Test.Parameters.default)和scalatest中,minSize为0。在生成任意余积时,Prop.check尝试在start up.MkCoproductArbitrary.ccons中生成具有此大小的值--在生成任意余积时,将大小解释为零作为终止条件,因此无法生成。作为临时解决方法,在Travis's response中的示例中,可以通过以下方式检查属性:
prop.check(Test.Parameters.default.withMinSize(1))使用scalatest (在问题中使用),最小大小参数也可以更改,可能是这样
implicit val config = PropertyCheckConfiguration(minSize = PosZInt(1))在测试用例之前(警告:我没有尝试/检查最具伸缩性的解决方案,不像之前的纯scalacheck解决方案)。
发布于 2016-11-04 04:15:00
这是一个巧妙的问题,我没有真正的解决方案,但它类似于我现在遇到的一些其他问题,因为我们在ScalaCheck 1.13中为函数提供了很好的Arbitrary实例。
首先,这里有一个不依赖于ScalaTest的最小化:
import org.scalacheck._, Shapeless._
sealed trait Foo; case object Bar extends Foo
val prop = Prop.forAll { (f: Int => Foo) => f(0); true }然后:
scala> prop.check
! Exception raised on property evaluation.
> ARG_0: <function1>
> Exception: org.scalacheck.Gen$RetrievalError: couldn't generate value
org.scalacheck.Gen.loop$1(Gen.scala:57)
org.scalacheck.Gen.doPureApply(Gen.scala:58)
...当我以前在1.13中遇到过这样的东西时,核心问题总是生成器在给定的大小为零时失败,而事实上,罪魁祸首似乎是this line中的标量形的case 0 => Gen.fail。
我们需要询问Alexandre来确认,但这似乎是一次事后的尝试,以避免递归ADT上的堆栈溢出。上下文是这样的:
Gen.sized {
case 0 => Gen.fail
case size =>
val sig = math.signum(size)
Gen.frequency(
1 -> Gen.resize(size - sig, Gen.lzy(headArbitrary.value.arbitrary)).map(Inl(_)),
n() -> Gen.resize(size - sig, Gen.lzy(tailArbitrary.arbitrary.arbitrary)).map(Inr(_))
)
}在当前的实现中,sig将始终为1,所以我猜最初case 0并不存在,其意图是做一些类似math.min(0, size - 1)的事情(对于非负整数,它将始终等于size - math.signum(size) )。
如果删除case 0 => Gen.fail行(或者将最小大小设置为大于零的值),代码就可以正常工作。问题是,如果没有case 0行,这样的ADT的派生实例可能会溢出堆栈:
sealed abstract class Tree
final case class Node(left: Tree, right: Tree, v: Int) extends Tree
case object Leaf extends Tree…因为即使size为0,您也可以继续选择递归Node分支。
您想要的是能够在size为零时说出类似“给我一个值,但不是来自递归构造函数”之类的语句,而乍一看,我看不出有什么方法可以实现这一点。
显然,在我们新的Cogen-ful世界里,case 0 => Gen.fail是不好的。如果我的库是scalacheck-shapeless,我可能会做一些可怕的事情,比如删除该行,然后捕获递归ADT的StackOverflowError和Gen.fail。这将是一个黑客,但它仍然比目前的情况更好。
我们可能应该在一个无标量的问题上讨论这个问题。同时,我只需要手动编写Or的Arbitrary (您应该能够派生出Right和Left,所以这并不是太糟糕)。
https://stackoverflow.com/questions/40390846
复制相似问题