2

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.

j3h
  • 679
  • 4
  • 10
  • Possible duplicate of [scalacheck case class random data generator](http://stackoverflow.com/questions/33370733/scalacheck-case-class-random-data-generator) – Rüdiger Klaehn Oct 27 '15 at 16:16
  • It's not really a duplicate, for two reasons: 1. I explicitly cannot use shapeless (much as I'd like to), and 2. I want to specify explicit generators rather than use arbitrary instances, which you can't do with Gen.resultOf, so neither of the answers to that question work for me. – j3h Nov 02 '15 at 21:29

1 Answers1

0

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)
}
Rüdiger Klaehn
  • 11,945
  • 2
  • 36
  • 55