4

I'm writing ScalaCheck generators for my domain models. For added flexibility, my generator-returning functions take specific values for the associations. For example:

case class A(...)
case class B(...)
case class C(a: A, ...)
case class D(b: B, c: C, ...)

val genA: Gen[A] = ???
val genB: Gen[B] = ???
val genC: (A) => Gen[C] = ???
val genD: (B, C) => Gen[D] = ???

This way, I have the option of generating arbitrary C's that are associated with a specific A. Often, though, I don't care what the model is associated with: I want to generate a C with any A. In that case, I can do one of these:

for {
  a <- genA
  c <- genC(a)
} yield c

// Or, desugared and simplified:
genA flatMap genC

// Or, with a Scalaz Monad for Gen
genA >>= genC
Kleisli(genC) =<< genA

I like having the latter options because I can include them in more complicated expressions without the relatively cumbersome for loop. However, I can't come up with any simple solutions for cases like D that take more than one argument. It seems I'm stuck with the for:

for {
  a <- genA
  b <- genB
  c <- genC(a)
  d <- genD(b, c)
} yield d

Bottom Line:

I would really appreciate it if there was some clean syntax for lifting not only a Kleisli arrow like f: T => M[R], but also a "Kleisli-like" function with n arguments g: (T, U) => M[R], onto a function over monadic values f2: M[T] => M[R] or g2: (M[T], M[U]) => M[R]. Something like this:

// liftX is a hypothetical lifting method enriched onto Function1-N:
genD.liftX(genB, genC.liftX(genA))

What I like about this hypothetical method is that it:

  • looks similar an unlifted function application, just with the liftX spliced in
  • is easy to compose into a complicated expression (see the lifting of genC inside the lifting of genD)

I could write it, but does something like this exist in the vast world of Scalaz? Would it help if I curried my multi-argument functions? Would Arrow combinators help? I've been browsing Scalaz for a while and haven't come up with anything similar, but I thought I'd ask in case anyone has run into this before.

Tim Yates
  • 5,151
  • 2
  • 29
  • 29
  • Well, you could lift `genD` and then flatten: `^(genB, genA >>= genC)(genD).join` (or perhaps more clearly `lift2(fD).apply(fB, fA >>= fC).join`). – Travis Brown Dec 16 '13 at 19:02
  • @TravisBrown True, and that's probably the closest I'll come in Scalaz. I might end up writing a `liftX` in those terms, if I can figure out what to call it (I just need something that's readable to people on my team who haven't delved into Scalaz). If you make your comment into an answer, I'll accept unless something else pops up. – Tim Yates Dec 16 '13 at 19:33

0 Answers0