¿Por qué necesitas arbitrarios en scalacheck?
random (3)
Me pregunto por qué se necesita Arbitrary porque las pruebas automáticas de propiedad requieren una definición de propiedad, como
val prop = forAll(v: T => check that property holds for v)
y generador de valor v. La guía del usuario dice que puede crear generadores personalizados para tipos personalizados (se ejemplifica un generador para árboles). Sin embargo, no explica por qué necesitas arbitrarios además de eso.
Aquí hay una pieza de manual.
implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))
Para obtener soporte para su propio tipo T, debe definir una definición implícita o un valor de tipo arbitrario [T]. Utilice el método de fábrica Arbitrario (...) para crear la instancia arbitraria. Este método toma un parámetro de tipo Gen [T] y devuelve una instancia de Arbitrary [T].
Claramente, dice que necesitamos arbitrariedad además de la Justificación de Gen. para arbitrario no es satisfactorio, aunque
El generador arbitrario es el generador utilizado por ScalaCheck cuando genera valores para los parámetros de propiedad.
En mi opinión, para utilizar los generadores, debe importarlos en lugar de incluirlos en arbitrarios. De lo contrario, se puede argumentar que tenemos que envolver los arbitrarios también en otra cosa para hacerlos utilizables (y así sucesivamente hasta el infinito envolviendo las envolturas sin fin).
También puede explicar cómo convierte arbitrary[Int]
tipo de argumento en generador. Es muy curioso y siento que estas son preguntas relacionadas.
Mi lectura de eso es que es posible que necesite tener varias instancias de Gen
, por lo que se usa Arbitrary
para "marcar" la que desea que use ScalaCheck?
ScalaCheck se ha portado desde la biblioteca de Haskell QuickCheck . En Haskell, las clases de tipo solo permiten una instancia para un tipo dado, lo que obliga a este tipo de separación. En Scala, sin embargo, no existe tal restricción y sería posible simplificar la biblioteca. Mi conjetura es que, al ser ScalaCheck (inicialmente escrito como) un mapeo 1-1 de QuickCheck, hace que sea más fácil para los Haskellers saltar a Scala :)
Aquí está la definición de Haskell de arbitraria
class Arbitrary a where
-- | A generator for values of the given type.
arbitrary :: Gen a
Y gen
newtype Gen a
Como puede ver, tienen una semántica muy diferente, Arbitrary es una clase de tipos y Gen una envoltura con un grupo de combinadores para construirlos.
Estoy de acuerdo en que el argumento de "limitar el alcance a través de la semántica" es un poco vago y no parece tomarse en serio cuando se trata de organizar el código: la clase arbitraria a veces simplemente delega a las instancias de Gen como en
/** Arbirtrary instance of Calendar */
implicit lazy val arbCalendar: Arbitrary[java.util.Calendar] =
Arbitrary(Gen.calendar)
Y a veces define su propio generador.
/** 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)))
}
Entonces, en efecto, esto lleva a la duplicación de código (cada Gen por defecto se refleja en un Arbitrary) y cierta confusión (¿por qué no es Arbitrary[BigInt]
envolver una Gen[BigInt]
defecto Gen[BigInt]
?).
forAll { v: T => ... }
se implementa con la ayuda de implícitos de Scala. Eso significa que el generador para el tipo T
se encuentra implícitamente en lugar de ser especificado explícitamente por el llamante.
Los implícitos de Scala son convenientes, pero también pueden ser problemáticos si no está seguro de qué valores o conversiones implícitos están actualmente en el alcance. Al usar un tipo específico ( Arbitrary
) para realizar búsquedas implícitas, ScalaCheck intenta restringir los impactos negativos del uso de implicits (este uso también lo hace similar a las clases de tipos de Haskell que son familiares para algunos usuarios).
Por lo tanto, tienes toda la razón de que Arbitrary
no es realmente necesario. El mismo efecto podría haberse logrado a través de Gen[T]
valores Gen[T]
implícitos, posiblemente con un poco más de confusión implícita en el alcance.
Como usuario final, debe pensar en Arbitrary[T]
como el generador predeterminado para el tipo T
Puede (a través del alcance) definir y usar múltiples instancias Arbitrary[T]
, pero no lo recomendaría. En su lugar, solo omita Arbitrary
y especifique sus generadores explícitamente:
val myGen1: Gen[T] = ... val mygen2: Gen[T] = ... val prop1 = forAll(myGen1) { t => ... } val prop2 = forAll(myGen2) { t => ... }
arbitrary[Int]
funciona igual que para forAll { n: Int => ... }
, solo busca la instancia Arbitrary[Int]
implícita y usa su generador. La implementación es simple:
def arbitrary[T](implicit a: Arbitrary[T]): Gen[T] = a.arbitrary
La implementación de Arbitrary
también podría ser útil aquí:
sealed abstract class Arbitrary[T] { val arbitrary: Gen[T] }