【问题标题】:Why do you need Arbitraries in scalacheck?为什么在 scalacheck 中需要 Arbitraries?
【发布时间】:2015-06-29 08:32:03
【问题描述】:

我想知道为什么需要 Arbitrary 因为自动化属性测试需要属性定义,比如

val prop = forAll(v: T => check that property holds for v)

和值 v 生成器。用户指南说您可以为自定义类型创建自定义生成器(示例为树的生成器)。然而,它并没有解释为什么你还需要任意性。

这是一份手册

implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))

要获得对您自己的类型 T 的支持,您需要定义一个隐式 def 或任意类型的 val[T]。使用工厂方法 Arbitrary(...) 创建任意实例。此方法采用一个类型的参数 Gen[T] 并返回 Arbitrary[T] 的一个实例。

它清楚地表明我们需要在 Gen 之上的 Arbitrary。但是,任意的理由并不令人满意

任意生成器是ScalaCheck使用的生成器 生成属性参数的值。

IMO,要使用生成器,您需要导入它们而不是将它们包装成任意的!否则,人们可能会争辩说,我们还需要将任意元素包装到其他东西中以使它们可用(以此类推,无休止地包装包装器)。

您还可以解释arbitrary[Int] 如何将参数类型转换为生成器。很好奇,感觉这些都是相关的问题。

【问题讨论】:

    标签: scala random scalacheck


    【解决方案1】:

    forAll { v: T => ... } 是在 Scala implicits 的帮助下实现的。这意味着 T 类型的生成器是隐式找到的,而不是由调用者显式指定的。

    Scala 隐式很方便,但如果您不确定当前范围内有哪些隐式值或转换,它们也会很麻烦。通过使用特定类型 (Arbitrary) 进行隐式查找,ScalaCheck 尝试限制使用隐式的负面影响(这种用法也使其类似于某些用户熟悉的 Haskell typeclasses)。

    因此,您完全正确地认为 Arbitrary 并不真正需要。可以通过隐含的 Gen[T] 值实现相同的效果,可以说是隐含的范围界定混乱。

    作为最终用户,您应该将Arbitrary[T] 视为T 类型的默认生成器。您可以(通过范围界定)定义和使用多个 Arbitrary[T] 实例,但我不建议这样做。相反,只需跳过 Arbitrary 并明确指定您的生成器:

    val myGen1: Gen[T] = ... val mygen2: Gen[T] = ... val prop1 = forAll(myGen1) { t => ... } val prop2 = forAll(myGen2) { t => ... }

    arbitrary[Int]forAll { n: Int => ... } 一样工作,它只是查找隐含的Arbitrary[Int] 实例并使用它的生成器。实现很简单:

    def 任意[T](隐式 a: Arbitrary[T]): Gen[T] = a.arbitrary

    Arbitrary 的实现在这里也可能会有所帮助:

    密封抽象类 Arbitrary[T] { 任意值:Gen[T] }

    【讨论】:

    • 等等。我错过了为什么任意比生成器更容易消除歧义。
    • 它们并不容易消除歧义,但它们通过语义限制了范围。当一个方法需要一个隐式的 Arbitrary 时,这意味着它需要一个类型的默认生成器。您可以想象另一个名为 EdgeCase 的类型类,其实现方式与 Arbitrary 类似,但其语义意图仅表示一种类型的边缘情况生成器。隐式 EdgeCase 值在隐式查找期间不会与隐式任意值竞争。
    • 看来,也可以请求一个隐式 Gen[T] 来请求默认值;然后,您可以根据需要显式传递默认值。
    【解决方案2】:

    ScalaCheck 已从Haskell QuickCheck library 移植。在 Haskell 中,类型类只允许给定类型的一个实例,迫使您进行这种分离。 但是在 Scala 中,没有这样的约束,并且可以简化库。我的猜测是,ScalaCheck 是(最初写为)QuickCheck 的 1-1 映射,让 Haskeller 更容易跳入 Scala :)

    这是任意的 Haskell 定义

    class Arbitrary a where
      -- | A generator for values of the given type.
      arbitrary :: Gen a
    

    和根

    newtype Gen a
    

    如您所见,它们具有非常不同的语义,Arbitrary 是一个类型类,而 Gen 是一个包装器,其中包含一堆组合器来构建它们。

    我同意“通过语义限制范围”的论点有点模糊,在组织代码时似乎没有被认真对待:Arbitrary 类有时只是简单地委托给 Gen 实例,如下所示

    /** Arbirtrary instance of Calendar */
    implicit lazy val arbCalendar: Arbitrary[java.util.Calendar] =
      Arbitrary(Gen.calendar)
    

    有时还会定义自己的生成器

    /** Arbitrary BigInt */
    implicit lazy val arbBigInt: Arbitrary[BigInt] = {
      val long: Gen[Long] =
        Gen.choose(Long.MinValue, Long.MaxValue).map(x => if (x == 0) 1L else x)
    
      val gen1: Gen[BigInt] = for { x <- long } yield BigInt(x)
      /* ... */
    
      Arbitrary(frequency((5, gen0), (5, gen1), (4, gen2), (3, gen3), (2, gen4)))
    }
    

    因此实际上这会导致代码重复(每个默认 Gen 都被任意镜像)和一些混乱(为什么 Arbitrary[BigInt] 不包装默认 Gen[BigInt]?)。

    【讨论】:

      【解决方案3】:

      我的理解是,您可能需要有多个 Gen 实例,所以 Arbitrary 用于“标记”您希望 ScalaCheck 使用的实例?

      【讨论】:

        猜你喜欢
        • 2020-05-03
        • 2019-06-09
        • 2013-06-12
        • 2012-11-21
        • 2011-04-13
        • 2014-03-04
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多