I am attempting to make a ScalaCheck matrix generator that will generate a 2D array/ matrix of a specified order (size/dim). I started with the example on the tutorial, and simplified it (for prototyping--I understand I should use sized to be able to resize future generators) to
def matrix[ T ]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Seq[ T ] ] ] =
Gen.listOfN( order, Gen.listOfN( order, g ) ).map(_.toArray)
However, I could not get an Array[Array[T]] from this without putting in a ClassTag:
def matrix[ T : ClassTag]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Seq[ T ] ] ] =
Gen.listOfN( order, Gen.listOfN( order, g ) ).map(_.toArray)
Which I sort-of understand--I'm not that experienced in Scala. In the process of getting there, I tried this:
def matrix2[ T : ClassTag]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Array[ T ] ] ] =
for {
rowSeq <- Gen.listOfN( order, g )
rowArray <- rowSeq.toArray
seqOfRowArrays <- Gen.listOfN( order, rowArray)
matrix <- seqOfRowArrays.toArray
} yield matrix
but the compiler complained
"type mismatch; found : Array[T] required: org.scalacheck.Gen[Array[Array[T]]]"
on the rowArray line and
"Multiple markers at this line
- type mismatch; found : org.scalacheck.Gen[Array[T]] required: scala.collection.GenTraversableOnce[?]"
on the seqOfRowArrays line.
My questions are:
1. Is there a simpler way to get a defined size Array[Array[T]], ie some way to bound Arbitrary?
2. What is wrong with the typing of the above for expression?
3. When tests fail, how will I keep ScalaCheck from reducing the order during shrinking?
#1: Don't worry about Arbitrary
v. Gen
. They are basically the same. There is a good answer explaining the distinctions between them here: Why do you need Arbitraries in scalacheck?
#2 You are mixing your monadic containers (Gen
and Array
) and scala is doing its best to combine them, and this gives you a confusing error.
To fix your type error, do this
def matrix2[ T : ClassTag]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Array[ T ] ] ] =
for {
rowSeq <- Gen.listOfN( order, g )
rowArray = rowSeq.toArray
seqOfRowArrays <- Gen.listOfN( order, rowArray)
} yield seqOfRowArrays.toArray
If you run this in the terminal you will notice something odd:
scala> matrix2(Gen.choose(1,10))(3)
res2: org.scalacheck.Gen[Array[Array[Int]]] = org.scalacheck.Gen$$anon$6@4c93778e
scala> res2.sample
res3: Option[Array[Array[Int]]] = Some([[I@1dd38904)
scala> res3.foreach{a2 => a2.foreach{a => a.foreach{i => println(s" $i")}; println("\n")}}
6
10
5
6
10
5
6
10
5
This only generates one row then repeats this same row throughout the matrix. My solution to this would be to use scalacheck to generate order^2 elements then group them into your 2D array structure after the fact. Here is a copy/paste from my Scala REPL where I did this:
scala> :paste
// Entering paste mode (ctrl-D to finish)
def matrix2[T: ClassTag](g: Gen[T])(order: Int): Gen[Array[Array[T]]] =
Gen.listOfN(order*order, g).map { squareList =>
squareList.toArray.grouped(order).toArray
}
// Exiting paste mode, now interpreting.
matrix2: [T](g: org.scalacheck.Gen[T])(order: Int)(implicit evidence$1: scala.reflect.ClassTag[T])org.scalacheck.Gen[Array[Array[T]]]
scala> matrix2(Gen.choose(1,10))(3)
res5: org.scalacheck.Gen[Array[Array[Int]]] = org.scalacheck.Gen$$anon$6@7519bc73
scala> res5.sample.foreach{a2 => a2.foreach{a => a.foreach{i => println(s" $i")}; println("\n")}}
1
9
9
7
6
10
10
2
6
#3 The order
will not change for this generator as you are using listOfN
method which fixes the size of the collection. The example in the user guide only resizes because it uses the Gen.sized
generator.