0

While trying to learn the ScalaCheck tool, I wrote two versions of a Map generator (I know there is one of these built in, but this was an exercise).

It seems that genMap0 and genMap00 should be equivalent, and genMap00 is bit cleaner, but in fact genMap0 works, but genMap00 fails miserably.

The yield is adorned with a println that can be turned on to see what happening (just edit the speak method), but even with this information I cannot say I really understand why the difference. This makes me think that another generator I try to write may also be flawed.

Can someone give a nice explanation of what is different between genMap0 and genMap00?

  import org.scalacheck._
  import Arbitrary._
  import Gen._
  import Prop._

  def speak(message: String): Unit = if (false) println(message)

  lazy val genMap0: Gen[Map[Int, Int]] = for {
    k <- arbitrary[Int]
    v <- arbitrary[Int]
    b <- arbitrary[Boolean]
    m <- if (b) value(Map.empty[Int, Int]) else genMap0
  } yield if (b) {
    speak("false"); m
  } else {
    speak("true"); m.updated(k, v)
  }

  lazy val genMap00: Gen[Map[Int, Int]] = for {
    k <- arbitrary[Int]
    v <- arbitrary[Int]
    m <- oneOf(Map.empty[Int, Int], genMap00)
  } yield if (m.isEmpty) {
    speak("empty:" + m); m
  } else {
    speak("not empty:" + m); m.updated(k, v)
  }

  val n = 5
  for (i <- 1 to n; m <- genMap0.sample) println(m)
  println("--------------")
  for (i <- 1 to n; m <- genMap00.sample) println(m)

This is the output (genMap00 always generates the empty map):

scala -cp scalacheck_2.10-1.10.1.jar
...
// Exiting paste mode, now interpreting.

Map()
Map(1 -> 1, 1530546613 -> -1889740266, -187647534 -> 0)
Map()
Map(-1 -> 2039603804)
Map(646468221 -> 1)
--------------
Map()
Map()
Map()
Map()
Map()
Mike Hanafey
  • 5,565
  • 4
  • 20
  • 26

1 Answers1

1

The problem the recursive generation always starts with an empty map, so gen00 always ends up with a generator that produces an empty map. The problem is the empty condition is also being used to detect termination.

This is fixed by gen000:

  lazy val genMap000: Gen[Map[Int, Int]] = for {
    k <- arbitrary[Int]
    v <- arbitrary[Int]
    m <- oneOf(None, genMap000.map(g => Some(g)))
  } yield (for (x <- m) yield x.updated(k, v)).getOrElse(Map())

This uses an intermediate Option[Map], with the None state indicating termination.

Using the explicit Boolean generator appears to be cleaner.

Mike Hanafey
  • 5,565
  • 4
  • 20
  • 26