使用shapeless-scalacheck派生任意函数实例

时间:2016-11-02 22:25:02

标签: scala shapeless scalacheck

升级到scalacheck ["Foo", "bar"].sort(Intl.Collator().compare); //["bar", "Foo"] 后,我遇到了一个奇怪的问题,即导出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] - 我尝试编写自己的泛型推导,看它是否有帮助,但它以完全相同的方式失败。

奇怪的东西,但可能是由于任意函数的工作方式,是函数实际生成,但在评估时失败。

感谢任何帮助/建议,因为我有点卡住了。

2 个答案:

答案 0 :(得分:3)

问题似乎是双边的:

  • scalacheck(org.scalacheck.Test.Parameters.default)和scalatest中的默认测试参数的minSize0Prop.check尝试在启动时生成此大小的值。

  • scalacheck-shapeless中的
  • MkCoproductArbitrary.ccons在生成任意副产品时将大小零解释为终止条件,因此失败。

作为临时解决方法,在Travis的回复示例中,可以通过以下方式检查该属性:

prop.check(Test.Parameters.default.withMinSize(1))

使用在问题中使用的scalatest,min size参数也可以更改,可能是通过输入类似

的内容
implicit val config = PropertyCheckConfiguration(minSize = PosZInt(1))

在测试用例之前(警告:我没有尝试/检查最新的解决方案,不像之前的纯scalacheck)。

答案 1 :(得分:2)

这是一个很好的问题,我没有真正的解决方案,但它与我现在遇到的其他一些问题类似,我们在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之前碰到这样的事情时,核心问题一直是当它给出的大小为零时失败的生成器,事实上罪魁祸首似乎是case 0 => Gen.fail在{ {3}}在scalacheck-shapeless。

我们需要请求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

...因为即使Nodesize,您仍可继续选择递归0分支。

size为零时,你想要的是能够说出“给我一个值而不是来自递归构造函数”这样的东西,而且我一眼就看不到一种方法发生。

显然case 0 => Gen.fail在我们新的Cogen - ful世界中很糟糕。如果scalacheck-shapeless是我的库,那么我很想做一些非常糟糕的事情,比如删除那行,然后抓住StackOverflowErrorGen.fail进行递归ADT。那将是一场黑客攻击,但它仍然比目前的情况要好。

我们应该把它放在一个scalacheck-shapedless问题中。在此期间,我只需手动为ArbitraryOr(您应该能够导出RightLeft,所以这不是太糟糕了。)