diff --git a/jvm/src/test/scala/org/scalacheck/GenSpecification.scala b/jvm/src/test/scala/org/scalacheck/GenSpecification.scala index 39dc92e0c..8290bd9b3 100644 --- a/jvm/src/test/scala/org/scalacheck/GenSpecification.scala +++ b/jvm/src/test/scala/org/scalacheck/GenSpecification.scala @@ -325,11 +325,37 @@ object GenSpecification extends Properties("Gen") { property("some") = forAll { n: Int => forAll(some(n)) { - case Some(m) if m == n => true + case Some(m) => m == n case _ => false } } + property("tailRecM") = forAll { (init: Int, seeds: List[Seed]) => + val g: ((Int, Int)) => Gen[Either[(Int, Int), Int]] = + { + case (c, x) if c <= 0 => + Gen.const(Right(x)) + case (c, x) => + val g = Gen.choose(Int.MinValue, x) + g.map { i => Left(((c - 1), i)) } + } + + val g1 = Gen.tailRecM((10, init))(g) + def g2(x: (Int, Int)): Gen[Int] = g(x).flatMap { + case Left(y) => g2(y) + case Right(x) => Gen.const(x) + } + + val finalG2 = g2((10, init)) + + + val params = Gen.Parameters.default + + seeds.forall { seed => + g1.pureApply(params, seed) == finalG2.pureApply(params, seed) + } + } + property("uuid version 4") = forAll(uuid) { _.version == 4 } property("uuid unique") = forAll(uuid, uuid) { diff --git a/src/main/scala/org/scalacheck/Gen.scala b/src/main/scala/org/scalacheck/Gen.scala index 2afc0990b..a6c59d365 100644 --- a/src/main/scala/org/scalacheck/Gen.scala +++ b/src/main/scala/org/scalacheck/Gen.scala @@ -467,6 +467,40 @@ object Gen extends GenArities{ g.map(b.fromIterable) } + /** Monadic recursion on Gen + * This is a stack-safe loop that is the same as: + * + * {{{ + * + * fn(a).flatMap { + * case Left(a) => tailRec(a)(fn) + * case Right(b) => Gen.const(b) + * } + * + * }}} + * + * which is useful for doing monadic loops without blowing up the + * stack + */ + def tailRecM[A, B](a0: A)(fn: A => Gen[Either[A, B]]): Gen[B] = { + @tailrec + def tailRecMR(a: A, seed: Seed, labs: Set[String])(fn: (A, Seed) => R[Either[A, B]]): R[B] = { + val re = fn(a, seed) + val nextLabs = labs | re.labels + re.retrieve match { + case None => r(None, re.seed).copy(l = nextLabs) + case Some(Right(b)) => r(Some(b), re.seed).copy(l = nextLabs) + case Some(Left(a)) => tailRecMR(a, re.seed, nextLabs)(fn) + } + } + + // This is the "Reader-style" appoach to making a stack-safe loop: + // we put one outer closure around an explicitly tailrec loop + gen[B] { (p: P, seed: Seed) => + tailRecMR(a0, seed, Set.empty) { (a, seed) => fn(a).doApply(p, seed) } + } + } + /** Wraps a generator lazily. The given parameter is only evaluated once, * and not until the wrapper generator is evaluated. */ def lzy[T](g: => Gen[T]): Gen[T] = {