How can I avoid boilerplate when generating case classes with ScalaCheck?

516 views Asked by At

I used to use an idiom like the following to generate case classes with ScalaCheck:

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

We recently upgraded ScalaCheck to 1.11, which removed the mapN methods. I'd really like to be able to avoid having to assign intermediate names to the generators for each field, and the mapN methods provided the easiest way to do that. Now, the best syntax is:

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

That's not so bad (for structures will a small number of constructor arguments), but I'd really like to make it clear that there's nothing special going on here, and I'm just specifying generators for each of the arguments without the reader of the code having to read through to confirm that.

In short, I'd like something akin to applicative syntax. Unfortunately, it's not an option to use scalaz, shapeless, or macros. I realize that that last sentence pretty much makes my question "how can I do X without access to the things that let me do X", but I'm hoping that someone will have a good idea.

1

There are 1 answers

0
Rüdiger Klaehn On

Since you are explicitly excluding libraries that are meant to prevent boilerplate, you will have to live with some boilerplate.

You can define gen combiners for each arity, using a similar approach to Gen.resultOf. In fact, you can just use Gen.resultOf, since the only difference to resultOf is that you want explicitly provided Gens instead of implicitly provided 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)
}