为什么在 scalacheck 中需要 Arbitraries?
Why do you need Arbitraries in scalacheck?
我想知道为什么需要 Arbitrary,因为自动化 属性 测试需要 属性 定义,例如
val prop = forAll(v: T => check that property holds for v)
和价值 v 发电机。用户指南说您可以为自定义类型创建自定义生成器(示例为树生成器)。然而,它并没有解释为什么你还需要任意性。
这是一份说明书
implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))
To get support for your own type T you need to define an implicit def
or val of type Arbitrary[T]. Use the factory method Arbitrary(...) to
create the Arbitrary instance. This method takes one parameter of type
Gen[T] and returns an instance of Arbitrary[T].
它清楚地表明我们需要在 Gen 之上的 Arbitrary。虽然对 arbitrary 的辩护并不令人满意,但是
The arbitrary generator is the generator used by ScalaCheck when it
generates values for property parameters.
IMO,要使用生成器,您需要导入它们而不是将它们包装成任意对象!否则,有人会争辩说我们需要将任意项也包装到其他东西中以使它们可用(等等,无限地包装包装器)。
您还可以解释 arbitrary[Int]
如何将参数类型转换为生成器。很好奇,感觉这些都是相关的问题。
我的理解是您可能需要 Gen
的多个实例,所以 Arbitrary
用于 "flag" 您希望 ScalaCheck 使用的实例?
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 arbitrary[T](implicit a: Arbitrary[T]): Gen[T] = a.arbitrary
Arbitrary
的实现在这里可能也有帮助:
sealed abstract class Arbitrary[T] {
val arbitrary: Gen[T]
}
ScalaCheck 已从 Haskell QuickCheck library 移植而来。在 Haskell type-classes 中只允许给定类型有一个实例,迫使您进行这种分离。
但是在 Scala 中,没有这样的约束,并且可以简化库。我的猜测是,ScalaCheck(最初写为)QuickCheck 的 1-1 映射,使 Haskell 用户更容易跳入 Scala :)
这是 Arbitrary
的 Haskell 定义
class Arbitrary a where
-- | A generator for values of the given type.
arbitrary :: Gen a
和一代
newtype Gen a
如您所见,它们具有非常不同的语义,Arbitrary 是一种类型 class,而 Gen 是一个带有一堆组合器的包装器来构建它们。
我同意 "limiting the scope through semantic" 的论点有点模糊,在组织代码时似乎没有被认真对待:Arbitrary class 有时只是简单地委托给 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 镜像)和一些混淆(为什么 Arbitrary[BigInt]
不包装默认 Gen[BigInt]
?)。
我想知道为什么需要 Arbitrary,因为自动化 属性 测试需要 属性 定义,例如
val prop = forAll(v: T => check that property holds for v)
和价值 v 发电机。用户指南说您可以为自定义类型创建自定义生成器(示例为树生成器)。然而,它并没有解释为什么你还需要任意性。
这是一份说明书
implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))
To get support for your own type T you need to define an implicit def or val of type Arbitrary[T]. Use the factory method Arbitrary(...) to create the Arbitrary instance. This method takes one parameter of type Gen[T] and returns an instance of Arbitrary[T].
它清楚地表明我们需要在 Gen 之上的 Arbitrary。虽然对 arbitrary 的辩护并不令人满意,但是
The arbitrary generator is the generator used by ScalaCheck when it generates values for property parameters.
IMO,要使用生成器,您需要导入它们而不是将它们包装成任意对象!否则,有人会争辩说我们需要将任意项也包装到其他东西中以使它们可用(等等,无限地包装包装器)。
您还可以解释 arbitrary[Int]
如何将参数类型转换为生成器。很好奇,感觉这些都是相关的问题。
我的理解是您可能需要 Gen
的多个实例,所以 Arbitrary
用于 "flag" 您希望 ScalaCheck 使用的实例?
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 arbitrary[T](implicit a: Arbitrary[T]): Gen[T] = a.arbitrary
Arbitrary
的实现在这里可能也有帮助:
sealed abstract class Arbitrary[T] { val arbitrary: Gen[T] }
ScalaCheck 已从 Haskell QuickCheck library 移植而来。在 Haskell type-classes 中只允许给定类型有一个实例,迫使您进行这种分离。 但是在 Scala 中,没有这样的约束,并且可以简化库。我的猜测是,ScalaCheck(最初写为)QuickCheck 的 1-1 映射,使 Haskell 用户更容易跳入 Scala :)
这是 Arbitrary
的 Haskell 定义class Arbitrary a where
-- | A generator for values of the given type.
arbitrary :: Gen a
和一代
newtype Gen a
如您所见,它们具有非常不同的语义,Arbitrary 是一种类型 class,而 Gen 是一个带有一堆组合器的包装器来构建它们。
我同意 "limiting the scope through semantic" 的论点有点模糊,在组织代码时似乎没有被认真对待:Arbitrary class 有时只是简单地委托给 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 镜像)和一些混淆(为什么 Arbitrary[BigInt]
不包装默认 Gen[BigInt]
?)。