9

I want to generate a list of integers corresponding to a list of generators in ScalaCheck.

    import org.scalacheck._
    import Arbitrary.arbitrary

    val smallInt = Gen.choose(0,10)
    val bigInt = Gen.choose(1000, 1000000)
    val zeroOrOneInt = Gen.choose(0, 1)
    val smallEvenInt = smallInt suchThat (_ % 2 == 0)

    val gens = List(smallInt, bigInt, zeroOrOneInt, smallEvenInt)
    //val listGen: Gen[Int] = ??
    //println(listGen.sample) //should print something like List(2, 2000, 0, 6)

For the given gens, I would like to create a generator listGen whose valid sample can be List(2, 2000, 0, 6). Here is my first attempt using tuples.

    val gensTuple = (smallInt, bigInt, zeroOrOneInt, smallEvenInt)
    val tupleGen = for {
        a <- gensTuple._1
        b <- gensTuple._2
        c <- gensTuple._3
        d <- gensTuple._4
    } yield (a, b, c, d)

    println(tupleGen.sample) // prints Some((1,318091,0,6))

This works, but I don't want to use tuples since the list of generators(gens) is created dynamically and the size of the list is not fixed. Is there a way to do it with Lists?

I want the use the generator of the list(listGen) in scalacheck forAll property checking.

This looks like a toy problem but this is the best I could do to create a standalone snippet reproducing the actual issue I am facing.

dips
  • 1,627
  • 14
  • 25

4 Answers4

14

How about using the Gen.sequence method? It transforms an Iterable[Gen[T]] into a Gen[C[T]], where C can be List:

  def sequence[C[_],T](gs: Iterable[Gen[T]])(implicit b: Buildable[T,C]): Gen[C[T]] = 
     ...
Eric
  • 15,494
  • 38
  • 61
  • 2
    Works fine! I had to explicitly add the implicit argument because of ambiguity with another method. `val listGen = Gen.sequence(gens)(util.Buildable.buildableList)` – dips Nov 25 '12 at 11:56
3

Just use Gen.sequence, but be careful as it will try to return a java.util.ArrayList[T] if you don't fully parameterize it (bug).

Full working example:

def genIntList(): Gen[List[Int]] = {

  val gens = List(Gen.chooseNum(1, 2), Gen.chooseNum(3, 4))

  Gen.sequence[List[Int], Int](gens)
}

println(genIntList.sample.get) // prints: List(1,4)
Joseph Lust
  • 19,340
  • 7
  • 85
  • 83
1

EDIT: Please disregard, this doesn't answer the asker's question


I can't comment on posts yet, so I'll have to venture a guess here. I presume the function 'sample' applies to the generators

Any reason why you can't do:

gens map (t=>t.sample)
Plasty Grove
  • 2,807
  • 5
  • 31
  • 42
  • 1
    I don't want create samples manually. It will be done by `scalacheck` when I use the generator as `forAll(listGen){ list => }` – dips Nov 25 '12 at 10:53
  • 1
    Ah sorry about that, didn't realise that scalacheck creates the samples – Plasty Grove Nov 25 '12 at 16:30
0

For a more theoretical answer: the method you want is traverse, which is equivalent to sequence compose map although it might be more efficient. It is of the general form:

def traverse[C[_]: Traverse, F[_]: Applicative, A, B](f: A => F[B], t: C[A]): F[C[B]]

It behaves like map but allows you to carry around some extra Applicative structure during the traversal, sequencing it along the way.

Ptharien's Flame
  • 3,246
  • 20
  • 24