Why do you need Arbitraries in scalacheck?

我怕爱的太早我们不能终老 提交于 2019-12-18 12:15:35

问题


I wonder why Arbitrary is needed because automated property testing requires property definition, like

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

and value v generator. The user guide says that you can create custom generators for custom types (a generator for trees is exemplified). Yet, it does not explain why do you need arbitraries on top of that.

Here is a piece of manual

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].

It clearly says that we need Arbitrary on top of Gen. Justification for arbitrary is not satisfactory, though

The arbitrary generator is the generator used by ScalaCheck when it generates values for property parameters.

IMO, to use the generators, you need to import them rather than wrapping them into arbitraries! Otherwise, one can argue that we need to wrap arbitraries also into something else to make them usable (and so on ad infinitum wrapping the wrappers endlessly).

You can also explain how does arbitrary[Int] convert argument type into generator. It is very curious and I feel that these are related questions.


回答1:


forAll { v: T => ... } is implemented with the help of Scala implicits. That means that the generator for the type T is found implicitly instead of being explicitly specified by the caller.

Scala implicits are convenient, but they can also be troublesome if you're not sure what implicit values or conversions currently are in scope. By using a specific type (Arbitrary) for doing implicit lookups, ScalaCheck tries to constrain the negative impacts of using implicits (this use also makes it similar to Haskell typeclasses that are familiar for some users).

So, you are entirely correct that Arbitrary is not really needed. The same effect could have been achieved through implicit Gen[T] values, arguably with a bit more implicit scoping confusion.

As an end-user, you should think of Arbitrary[T] as the default generator for the type T. You can (through scoping) define and use multiple Arbitrary[T] instances, but I wouldn't recommend it. Instead, just skip Arbitrary and specify your generators explicitly:

val myGen1: Gen[T] = ...
val mygen2: Gen[T] = ...

val prop1 = forAll(myGen1) { t => ... }
val prop2 = forAll(myGen2) { t => ... }

arbitrary[Int] works just like forAll { n: Int => ... }, it just looks up the implicit Arbitrary[Int] instance and uses its generator. The implementation is simple:

def arbitrary[T](implicit a: Arbitrary[T]): Gen[T] = a.arbitrary

The implementation of Arbitrary might also be helpful here:

sealed abstract class Arbitrary[T] {
  val arbitrary: Gen[T]
}



回答2:


ScalaCheck has been ported from the Haskell QuickCheck library. In Haskell type-classes only allow one instance for a given type, forcing you into this sort of separation. In Scala though, there isn't such a constraint and it would be possible to simplify the library. My guess is that, ScalaCheck being (initially written as) a 1-1 mapping of QuickCheck, makes it easier for Haskellers to jump into Scala :)

Here is the Haskell definition of Arbitrary

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

And Gen

newtype Gen a

As you can see they have a very different semantic, Arbitrary being a type class, and Gen a wrapper with a bunch of combinators to build them.

I agree that the argument of "limiting the scope through semantic" is a bit vague and does not seem to be taken seriously when it comes to organizing the code: the Arbitrary class sometimes simply delegates to Gen instances as in

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

and sometimes defines its own generator

/** 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)))
}

So in effect this leads to code duplication (each default Gen being mirrored by an Arbitrary) and some confusion (why isn't Arbitrary[BigInt] not wrapping a default Gen[BigInt]?).




回答3:


My reading of that is that you might need to have multiple instances of Gen, so Arbitrary is used to "flag" the one that you want ScalaCheck to use?



来源:https://stackoverflow.com/questions/31111106/why-do-you-need-arbitraries-in-scalacheck

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!