Зачем вам нужны Арбитражные решения в scalacheck?

Интересно, почему требуется произвольное, потому что для автоматического тестирования свойств требуется определение свойства, например

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

и генератора значений v. В руководстве пользователя говорится, что вы можете создавать настраиваемые генераторы для пользовательских типов (например, генератор для деревьев). Тем не менее, это не объясняет, почему вам нужны альтернативы.

Вот часть руководства

implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))

Чтобы получить поддержку вашего собственного типа T, вам нужно определить неявное определение или val типа Arbitrary [T]. Используйте метод factory Произвольный (...) для создать произвольный экземпляр. Этот метод принимает один параметр типа Gen [T] и возвращает экземпляр произвольного [T].

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

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

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

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

Ответы

Ответ 1

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

Scala implicits удобны, но они также могут быть неприятными, если вы не уверены, какие неявные значения или преобразования в настоящее время находятся в области видимости. Используя специальный тип (Arbitrary) для выполнения неявного поиска, ScalaCheck пытается ограничить негативные последствия использования implicits (это использование также делает его похожим на классные классы 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]
}

Ответ 2

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

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

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

И Gen

newtype Gen a

Как вы можете видеть, у них очень разный семантический, произвольный тип типа, а Gen - оболочка с кучей комбинаторов для их создания.

Я согласен, что аргумент "ограничение сферы действия семантикой" немного расплывчатый и, похоже, не воспринимается всерьез, когда дело доходит до организации кода: произвольный класс иногда просто делегирует экземпляры 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[BigInt] не обертывание по умолчанию Gen[BigInt]?).

Ответ 3

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