Skip to content

Commit b817db8

Browse files
committed
improve tests and fix implementations
1 parent 5336cf0 commit b817db8

File tree

2 files changed

+40
-16
lines changed

2 files changed

+40
-16
lines changed

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

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -323,24 +323,34 @@ 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
326+
property("product") = forAll { (a: Int, b: Int, seeds: List[Seed]) =>
327+
val ga = Gen.choose(Int.MinValue, a)
328+
val gb = Gen.choose(Int.MinValue, b)
329+
330+
val prod1 = Gen.product(ga, gb)
331+
val prod2 = ga.flatMap { a => gb.map((a, _)) }
332+
333+
val params = Gen.Parameters.default
334+
seeds.forall { seed =>
335+
prod1.pureApply(params, seed) == prod2.pureApply(params, seed)
329336
}
330337
}
331338

332339
property("some") = forAll { n: Int =>
333340
forAll(some(n)) {
334-
case Some(m) if m == n => true
341+
case Some(m) => m == n
335342
case _ => false
336343
}
337344
}
338345

339-
property("tailRecM") = forAll { (init: Int) =>
346+
property("tailRecM") = forAll { (init: Int, seeds: List[Seed]) =>
340347
val g: ((Int, Int)) => Gen[Either[(Int, Int), Int]] =
341348
{
342-
case (c, x) if c <= 0 => Right(x)
343-
case (c, x) => Left((c - 1, x * Int.MaxValue))
349+
case (c, x) if c <= 0 =>
350+
Gen.const(Right(x))
351+
case (c, x) =>
352+
val g = Gen.choose(Int.MinValue, x)
353+
g.map { i => Left(((c - 1), i)) }
344354
}
345355

346356
val g1 = Gen.tailRecM((10, init))(g)
@@ -349,7 +359,14 @@ object GenSpecification extends Properties("Gen") {
349359
case Right(x) => Gen.const(x)
350360
}
351361

352-
forAll(g1, g2((10, init)))(_ == _)
362+
val finalG2 = g2((10, init))
363+
364+
365+
val params = Gen.Parameters.default
366+
367+
seeds.forall { seed =>
368+
g1.pureApply(params, seed) == finalG2.pureApply(params, seed)
369+
}
353370
}
354371

355372
property("uuid version 4") = forAll(uuid) { _.version == 4 }

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -484,28 +484,35 @@ object Gen extends GenArities{
484484
*/
485485
def tailRecM[A, B](a0: A)(fn: A => Gen[Either[A, B]]): Gen[B] = {
486486
@tailrec
487-
def tailRecMR[A, B](a: A, labs: Set[String])(fn: A => R[Either[A, B]]): R[B] = {
488-
val re = fn(a)
487+
def tailRecMR(a: A, seed: Seed, labs: Set[String])(fn: (A, Seed) => R[Either[A, B]]): R[B] = {
488+
val re = fn(a, seed)
489489
val nextLabs = labs | re.labels
490490
re.retrieve match {
491491
case None => r(None, re.seed).copy(l = nextLabs)
492492
case Some(Right(b)) => r(Some(b), re.seed).copy(l = nextLabs)
493-
case Some(Left(a)) => tailRecMR(a, nextLabs)(fn)
493+
case Some(Left(a)) => tailRecMR(a, re.seed, nextLabs)(fn)
494494
}
495495
}
496496

497+
// This is the "Reader-style" appoach to making a stack-safe loop:
498+
// we put one outer closure around an explicitly tailrec loop
497499
gen[B] { (p: P, seed: Seed) =>
498-
tailRecMR(a0, Set.empty) { a => fn(a).doApply(p, seed) }
500+
tailRecMR(a0, seed, Set.empty) { (a, seed) => fn(a).doApply(p, seed) }
499501
}
500502
}
501503

502504
/** Build a pair from two generators
503505
*/
504506
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)
507+
gen[(A, B)] { (p: P, seed: Seed) =>
508+
val ra = ga.doApply(p, seed)
509+
ra.retrieve match {
510+
case None => r(None, ra.seed).copy(l = ra.labels)
511+
case Some(a) =>
512+
val rb = gb.doApply(p, ra.seed)
513+
rb.map((a, _)).copy(l = ra.labels | rb.labels)
514+
}
515+
}
509516

510517
/** Wraps a generator lazily. The given parameter is only evaluated once,
511518
* and not until the wrapper generator is evaluated. */

0 commit comments

Comments
 (0)