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.
forAll { v: T => ... }
is implemented with the help of Scala implicits. That means that the generator for the typeT
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 implicitGen[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 typeT
. You can (through scoping) define and use multipleArbitrary[T]
instances, but I wouldn't recommend it. Instead, just skipArbitrary
and specify your generators explicitly:arbitrary[Int]
works just likeforAll { n: Int => ... }
, it just looks up the implicitArbitrary[Int]
instance and uses its generator. The implementation is simple:The implementation of
Arbitrary
might also be helpful here: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
And Gen
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
and sometimes defines its own generator
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 defaultGen[BigInt]
?).My reading of that is that you might need to have multiple instances of
Gen
, soArbitrary
is used to "flag" the one that you want ScalaCheck to use?