Skip to content

Commit 5336cf0

Browse files
committed
Add Gen.product and Gen.tailRecM
1 parent c59ae70 commit 5336cf0

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

jvm/src/test/scala/org/scalacheck/GenSpecification.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,35 @@ object GenSpecification extends Properties("Gen") {
323323
}
324324
}
325325

326+
property("product") = forAll { (a: Int, b: Int) =>
327+
forAll(Gen.product(a, b)) {
328+
case (a1, b1) => a1 == a && b1 == b
329+
}
330+
}
331+
326332
property("some") = forAll { n: Int =>
327333
forAll(some(n)) {
328334
case Some(m) if m == n => true
329335
case _ => false
330336
}
331337
}
332338

339+
property("tailRecM") = forAll { (init: Int) =>
340+
val g: ((Int, Int)) => Gen[Either[(Int, Int), Int]] =
341+
{
342+
case (c, x) if c <= 0 => Right(x)
343+
case (c, x) => Left((c - 1, x * Int.MaxValue))
344+
}
345+
346+
val g1 = Gen.tailRecM((10, init))(g)
347+
def g2(x: (Int, Int)): Gen[Int] = g(x).flatMap {
348+
case Left(y) => g2(y)
349+
case Right(x) => Gen.const(x)
350+
}
351+
352+
forAll(g1, g2((10, init)))(_ == _)
353+
}
354+
333355
property("uuid version 4") = forAll(uuid) { _.version == 4 }
334356

335357
property("uuid unique") = forAll(uuid, uuid) {

src/main/scala/org/scalacheck/Gen.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,46 @@ object Gen extends GenArities{
467467
g.map(b.fromIterable)
468468
}
469469

470+
/** Monadic recursion on Gen
471+
* This is a stack-safe loop that is the same as:
472+
*
473+
* {{{
474+
*
475+
* fn(a).flatMap {
476+
* case Left(a) => tailRec(a)(fn)
477+
* case Right(b) => Gen.const(b)
478+
* }
479+
*
480+
* }}}
481+
*
482+
* which is useful for doing monadic loops without blowing up the
483+
* stack
484+
*/
485+
def tailRecM[A, B](a0: A)(fn: A => Gen[Either[A, B]]): Gen[B] = {
486+
@tailrec
487+
def tailRecMR[A, B](a: A, labs: Set[String])(fn: A => R[Either[A, B]]): R[B] = {
488+
val re = fn(a)
489+
val nextLabs = labs | re.labels
490+
re.retrieve match {
491+
case None => r(None, re.seed).copy(l = nextLabs)
492+
case Some(Right(b)) => r(Some(b), re.seed).copy(l = nextLabs)
493+
case Some(Left(a)) => tailRecMR(a, nextLabs)(fn)
494+
}
495+
}
496+
497+
gen[B] { (p: P, seed: Seed) =>
498+
tailRecMR(a0, Set.empty) { a => fn(a).doApply(p, seed) }
499+
}
500+
}
501+
502+
/** Build a pair from two generators
503+
*/
504+
def product[A, B](ga: Gen[A], gb: Gen[B]): Gen[(A, B)] =
505+
for {
506+
a <- ga
507+
b <- gb
508+
} yield (a, b)
509+
470510
/** Wraps a generator lazily. The given parameter is only evaluated once,
471511
* and not until the wrapper generator is evaluated. */
472512
def lzy[T](g: => Gen[T]): Gen[T] = {

0 commit comments

Comments
 (0)