Как я могу избежать шаблонного кода при создании классов случаев с помощью ScalaCheck?

Раньше я использовал идиому, подобную следующей, для создания классов case с помощью ScalaCheck:

GenSomething.map2(GenSomethingElse)(MyClass(_, _))

Недавно мы обновили ScalaCheck до версии 1.11, в которой были удалены методы mapN. Мне бы очень хотелось иметь возможность избежать назначения промежуточных имен генераторам для каждого поля, и методы mapN предоставили самый простой способ сделать это. Теперь лучший синтаксис:

for {
  something <- GenSomething
  somethingElse <- GenSomethingElse
} yield MyClass(
  something = something,
  somethingElse = somethingElse)

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

Короче говоря, я хотел бы что-то вроде аппликативного синтаксиса. К сожалению, нельзя использовать scalaz, shapeless или макросы. Я понимаю, что последнее предложение в значительной степени вызывает у меня вопрос «как я могу сделать X, не имея доступа к вещам, которые позволяют мне делать X», но я надеюсь, что у кого-то появится хорошая идея.


person j3h    schedule 20.11.2014    source источник
comment
Возможный дубликат генератора случайных данных класса case scalacheck   -  person Rüdiger Klaehn    schedule 27.10.2015
comment
На самом деле это не дубликат по двум причинам: 1. Я явно не могу использовать shapeless (как бы мне этого ни хотелось) и 2. Я хочу указать явные генераторы, а не использовать произвольные экземпляры, что вы не можете сделать с Gen .resultOf, поэтому ни один из ответов на этот вопрос не работает для меня.   -  person j3h    schedule 03.11.2015


Ответы (1)


Поскольку вы явно исключаете библиотеки, предназначенные для предотвращения использования шаблонов, вам придется смириться с некоторыми шаблонами.

Вы можете определить объединители генов для каждой арности, используя подход, аналогичный подходу Gen.resultOf. На самом деле вы можете просто использовать Gen.resultOf, поскольку единственное отличие от resultOf заключается в том, что вам нужны явно предоставленные Gens вместо неявно предоставленных Arbitrarys.

object GenCombiner {

  def zipMap[A, R](a: Gen[A])(f: A ⇒ R): Gen[R] =
    Gen.resultOf(f)(Arbitrary(a))

  def zipMap[A, B, R](a: Gen[A], b: Gen[B])(f: (A, B) ⇒ R): Gen[R] =
    Gen.resultOf(f)(Arbitrary(a), Arbitrary(b))

  def zipMap[A, B, C, R](a: Gen[A], b: Gen[B], c: Gen[C])(f: (A, B, C) ⇒ R): Gen[R] =
    Gen.resultOf(f)(Arbitrary(a), Arbitrary(b), Arbitrary(c))

  // other arities
}

object GenCombinerTest {
  import GenCombiner._

  case class Foo(alpha: String, num: String)

  val fooGen: Gen[Foo] = zipMap(Gen.alphaStr, Gen.numStr)(Foo)
}
person Rüdiger Klaehn    schedule 03.11.2015