Зачем вам нужны библиотеки в scalacheck?

Интересно, зачем нужен 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 типа Arbitrary [T]. Используйте фабричный метод Arbitrary (...) для создания экземпляра Arbitrary. Этот метод принимает один параметр типа Gen [T] и возвращает экземпляр Arbitrary [T].

В нем ясно сказано, что нам нужен произвольный поверх общего. Обоснование произвольного не является удовлетворительным, хотя

Произвольный генератор - это генератор, используемый ScalaCheck при генерации значений для параметров свойств.

IMO, чтобы использовать генераторы, вам нужно импортировать их, а не оборачивать их в произвольные! В противном случае можно утверждать, что нам нужно обернуть произвольные файлы во что-то еще, чтобы сделать их пригодными для использования (и так до бесконечности, обертывая обертки до бесконечности).

Вы также можете объяснить, как arbitrary[Int] преобразовывает тип аргумента в генератор. Это очень любопытно, и я чувствую, что это связанные вопросы.


person Val    schedule 29.06.2015    source источник


Ответы (3)


forAll { v: T => ... } реализован с помощью Scala implits. Это означает, что генератор для типа T найден неявно, а не явно указан вызывающей стороной.

Неявные значения Scala удобны, но они также могут вызывать проблемы, если вы не уверены, какие неявные значения или преобразования в настоящее время находятся в области видимости. Используя определенный тип (Arbitrary) для выполнения неявных поисков, ScalaCheck пытается ограничить негативные последствия использования имплицитов (это использование также делает его похожим на классы типов Haskell, знакомые некоторым пользователям).

Итак, вы совершенно правы, что 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]
}
person rickynils    schedule 30.06.2015
comment
Ждать. Я упустил, почему арбитров легче устранить неоднозначность, чем генераторы. - person Val; 01.07.2015
comment
Их нелегко устранить неоднозначность, но они ограничивают область видимости посредством семантики. Когда метод требует неявного произвольного значения, это означает, что ему нужен генератор по умолчанию для типа. Вы можете представить себе другой класс типа под названием EdgeCase, реализованный так же, как Arbitrary, но с семантическим намерением представлять только генераторы крайних регистров для типа. Тогда неявные значения EdgeCase не будут конкурировать с неявными произвольными значениями во время неявного поиска. - person rickynils; 02.07.2015
comment
Похоже, можно было бы с таким же успехом запросить неявный Gen [T], чтобы запросить значение по умолчанию; затем вы можете явно передать значение по умолчанию, если хотите. - person Blaisorblade; 01.11.2017

ScalaCheck был перенесен из библиотеки Haskell QuickCheck. В классах типов Haskell разрешен только один экземпляр для данного типа, что вынуждает вас к такому разделению. Однако в Scala такого ограничения нет, и можно было бы упростить библиотеку. Я предполагаю, что ScalaCheck (изначально записанный как) отображение 1-1 для QuickCheck упрощает для Haskellers переход на Scala :)

Вот определение произвольного

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

И Gen

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

Фактически это приводит к дублированию кода (каждое поколение по умолчанию зеркалируется произвольным) и некоторой путанице (почему Arbitrary[BigInt] не обертывает Gen[BigInt] по умолчанию?).

person Bruno Bieth    schedule 17.03.2017

Я считаю, что вам может потребоваться несколько экземпляров Gen, поэтому Arbitrary используется для «пометки» того, который вы хотите использовать в ScalaCheck?

person lmm    schedule 29.06.2015