Cleaner method to generate two Lists of the same length

308 views
Skip to first unread message

Marc

unread,
May 26, 2011, 8:43:53 PM5/26/11
to scala...@googlegroups.com
Dear all,

I have been playing around with ScalaCheck for a bit now and am curious as to whether there is a cleaner means
of coding the following:  I have implemented a mathematical vector class and I wish to test the addition
of two vectors of the same length.  For this I require the generation of two vectors with equal lengths that can be added.

Simplifying the problem, say we wish to test the following property.  Given List v1 and List v2, we wish to verify
that v1.length + v2.length == 2*v1.length.

To do this, I implemented the following:

Generator:
def generateTwoEqualLengthLists(size:Int,g:Gen[Double]) = for{v1 <- Gen.listOfN(size,g); v2<-Gen.listOfN(size,g)} yield (v1,v2)

Property:
val propLengthTest = forAll(generateTwoEqualLengthLists(5,Gen.choose(-10,10))) { 
        v:(List[Double],List[Double])=> v._1.length + v._2.length == 2*v._1.length}

And this works.  However, I would prefer to have a property of the form:
     forAll(generator){ (v1:List[Double], v2:List[Double]) => v1.length + v2.length == 2*v1.length}
with explicit naming of the lists.  When I use the generator above, the types do not match up
and I receive the error
 found   : (List[Double], List[Double]) => Boolean
 required: ((List[Double], List[Double])) => ?

All my attempts at fixing this have failed.  I am sure this is due to confusion on my part and any
help/hints/paths would be greatly appreciated.

Thank you in advance!

marc



Matthew Pocock

unread,
May 27, 2011, 3:41:58 AM5/27/11
to scala...@googlegroups.com
I don't know what the 'right' way is, but you could have a generator that makes a single list of pairs, and then unzips that into two lists.

Matthew



--
You received this message because you are subscribed to the Google Groups "scalacheck" group.
To post to this group, send email to scala...@googlegroups.com.
To unsubscribe from this group, send email to scalacheck+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/scalacheck?hl=en.



--
Matthew Pocock
(0191) 2566550

Rickard Nilsson

unread,
May 27, 2011, 6:29:08 AM5/27/11
to scala...@googlegroups.com
Hi,

On Thu, 26 May 2011 17:43:53 -0700 (PDT), Marc <mill...@gmail.com>
wrote:


> Dear all,
>
> I have been playing around with ScalaCheck for a bit now and am
> curious as to whether there is a cleaner means
> of coding the following: I have implemented a mathematical vector
> class and I wish to test the addition
> of two vectors of the same length. For this I require the generation
> of two vectors with equal lengths that can be added.
>
> Simplifying the problem, say we wish to test the following property.
> Given List v1 and List v2, we wish to verify
> that v1.length + v2.length == 2*v1.length.
>
> To do this, I implemented the following:
>
> Generator:
> def generateTwoEqualLengthLists(size:Int,g:Gen[Double]) = for{v1

> v1.length + v2.length == 2*v1.length}
> with explicit naming of the lists. When I use the generator above,
> the
> types do not match up
> and I receive the error
>
> found : (List[Double], List[Double]) => Boolean
> required: ((List[Double], List[Double])) => ?
>
> All my attempts at fixing this have failed. I am sure this is due to
> confusion on my part and any
> help/hints/paths would be greatly appreciated.

You can use a case-expression to match the pair of lists. Here is a
solution, with a generic generator:

import org.scalacheck._

def g[T](implicit a: Arbitrary[T]) = for {
v1 <- Arbitrary.arbitrary[List[T]]
v2 <- Gen.containerOfN[List,T](v1.length, Arbitrary.arbitrary[T])
} yield (v1,v2)

val propLengthTest = Prop.forAll(g[Double]) { case (v1,v2) =>


v1.length + v2.length == 2*v1.length
}


/ Rickard


> Thank you in advance!
>
> marc
>

Marc

unread,
May 27, 2011, 8:56:10 AM5/27/11
to scala...@googlegroups.com



 You can use a case-expression to match the pair of lists. Here is a
 solution, with a generic generator:

 import org.scalacheck._

 def g[T](implicit a: Arbitrary[T]) = for {
   v1 <- Arbitrary.arbitrary[List[T]]
   v2 <- Gen.containerOfN[List,T](v1.length, Arbitrary.arbitrary[T])
 } yield (v1,v2)

 val propLengthTest = Prop.forAll(g[Double]) { case (v1,v2) =>
   v1.length + v2.length == 2*v1.length
 }

 


Wow, thank you I was very close to this, but did not use the case-expression.
So that I can learn a little more from this, would you please explain the semantic/type difference between 

  forAll...{ (v1,v2)  and forAll...{case (v1,v2)}

and how the case-expression works in this instance.

Again, I really appreciate your help and the hard work you have put into this tool.

Thank you,
marc

Rickard Nilsson

unread,
May 27, 2011, 10:02:51 AM5/27/11
to scala...@googlegroups.com
On Fri, 27 May 2011 05:56:10 -0700 (PDT), Marc <mill...@gmail.com>
wrote:

>> You can use a case-expression to match the pair of lists. Here is a
>> solution, with a generic generator:
>>
>> import org.scalacheck._
>>
>> def g[T](implicit a: Arbitrary[T]) = for {
>> v1 <- Arbitrary.arbitrary[List[T]]
>> v2 <- Gen.containerOfN[List,T](v1.length, Arbitrary.arbitrary[T])
>> } yield (v1,v2)
>>
>> val propLengthTest = Prop.forAll(g[Double]) { case (v1,v2) =>
>> v1.length + v2.length == 2*v1.length
>> }
>>
>
> Wow, thank you I was very close to this, but did not use the
> case-expression.
> So that I can learn a little more from this, would you please explain
> the semantic/type difference between
>
> forAll...{ (v1,v2) and forAll...{case (v1,v2)}
>
> and how the case-expression works in this instance.


This is really a Scala feature. Look at the following REPL session:


scala> val f = { (n: Int, m: Int) => "foo" }
f: (Int, Int) => java.lang.String = <function2>

scala> val g: ((Int,Int) => String) = { case (n: Int, m: Int) => "foo"
}
g: (Int, Int) => String = <function2>

scala> val h: (Tuple2[Int,Int] => String) = { case (n: Int, m: Int) =>
"foo" }
h: ((Int, Int)) => String = <function1>


The case-expression creates a partial function, but by using an
explicit type you can tell Scala to convert it into either a function
that takes several arguments, or a function that takes one
tuple-argument. Since your generator generates a Tuple2, Scala converts
the case-expression into a function that takes a Tuple2.

/ Rickard

Marc

unread,
May 27, 2011, 10:07:52 AM5/27/11
to scala...@googlegroups.com
Agh, thank you !  IT was the conversion that I was forgetting about.  Again, thank you.

marc
 

Marc

unread,
May 27, 2011, 10:28:56 AM5/27/11
to scala...@googlegroups.com
Sorry for the spam,  but I am now encountering some weird behavior when using this generator.

  def g[T](implicit a: Arbitrary[T]) = for {
          v1 <- Arbitrary.arbitrary[List[T]]                                                       
          v2 <- Gen.containerOfN[List,T](v1.length, Arbitrary.arbitrary[T])
   } yield (v1,v2)


I have a class MathVector(data:ParSeq[Double]) is basically a wrapper that defines addition(+) and subtraction(-).  It passes all of its unit tests I wrote using ScalaTest.   

The first property passes all tests and the generator generates  tuples of lists of the same size.  

         val propAdd = forAll(g[Double]){
              case (v1,v2) =>
                val vec1 = MathVector(v1.par)
                val vec2 = MathVector(v2.par)
                val vec1PlusVec2 = vec1+vec2;
                val M = vec1PlusVec2 - vec2
                println(v1.length+"=" + vec1.length + " " + v2.length +"="+vec2.length + " " + vec1PlusVec2.length + " " + M.length)        
                v1.length == vec1.length && vec1.length == vec2.length && 
                    vec1PlusVec2.length == vec2.length
            }
            propAdd.check                                                                          
    gives output:
       7=7 7=7 7 7
       7=7 7=7 7 7 
      11=11 11=11 11 11 
      1=1 1=1 1 1
      6=6 6=6 6 6
     10=10 10=10 10 10
     11=11 11=11 11 11
      0=0 0=0 0 0

However, know if I add one more test to the property as follows (and turn off the length exception I have in the MathVector class)

         val propAdd2 = forAll(g[Double]){
              case (v1,v2) =>
                val vec1 = MathVector(v1.par)
                val vec2 = MathVector(v2.par)
                val vec1PlusVec2 = vec1+vec2;
                val M = vec1PlusVec2 - vec2
                println(v1.length+"=" + vec1.length + " " + v2.length +"="+vec2.length + " " + vec1PlusVec2.length + " " + M.length)        
                v1.length == vec1.length && vec1.length == vec2.length && 
                    vec1PlusVec2.length == vec2.length && 
M == vec1  //NEW PROPERTY TO TEST
            }
            propAdd2.check            

the generator starts to generate tuples with different sized lists
 
1=1 1=1 1 1
2=2 2=2 2 2
3=3 3=3 3 3
1=1 3=3 1 1   <= lists of different sizes
0=0 3=3 0 0    <= lists of different sizes                                                          


Is there something obvious I am missing (I do have an == method in the class and have tested that).  I just dont see how
that one additional check can affect the generator.


Rickard Nilsson

unread,
May 27, 2011, 11:11:49 AM5/27/11
to scala...@googlegroups.com
Hi Marc,

On Fri, 27 May 2011 07:28:56 -0700 (PDT), Marc <mill...@gmail.com>
wrote:

> Sorry for the spam, but I am now encountering some weird behavior
> when using this generator.

It's not spam, it's feedback. Keep it coming!

I think this is a consequence of ScalaCheck's "shrinking" feature. When
ScalaCheck finds a failing test case, it tries to shrink the input
argument, to find a minimal failing test case. It is a nice feature, but
it is easy to get caught like you did here. The problem is that
ScalaCheck doesn't know that the pair of lists should contain lists of
equal lengths, so when it tries to shrink the lists it might well end up
with one list of length 1 and one of length 3. The solution to this
problem is to add a precondition to your property. This makes ScalaCheck
ignore cases when the precondition isn't fulfilled. In your case it
would look something like this (you need tom import Prop._ to get the
implicit support for the ==> operator):

import Prop._

val propAdd = forAll(g[Double]) { case (v1,v2) =>

(v1.length == v2.length) ==> { // PRECONDITION


val vec1 = MathVector(v1.par)
val vec2 = MathVector(v2.par)
val vec1PlusVec2 = vec1+vec2;
val M = vec1PlusVec2 - vec2

v1.length == vec1.length && vec1.length == vec2.length &&
vec1PlusVec2.length == vec2.length && M == vec1
}
}

I think this would solve your problem. However, this is not the most
convenient thing to do, since the precondition essentially is specified
_both_ in the generator and in the property. It would be very nice to
only do it in one place. Then you would get sort of "implicit
preconditions" by using generators such as Gen.posNum. This has been
discussed before, see
http://groups.google.com/group/scalacheck/browse_thread/thread/396e5e81e5909884.
I think it would be possible to add some kind of "generator
invariants", that the shrinker respects. I will add a ticket for this
and start to think about how to implement it.

Of course, sometimes you do use general generators because it really
isn't worthwhile to create a custom generators. Then a precondition on
the property is the correct choice. But when the precondition gets
complex you probably need to define a custom generator because
ScalaCheck can't come up with valid inputs with its random approach. In
such cases it would be nice to only put the precondition on the
generator and not also on the property. Hmm, I don't now if this
paragraph is just a reiteration of what I said above, I just sort of
kept writing... Well, I hope you get what I mean.

/ Rickard

Marc

unread,
May 27, 2011, 11:22:14 AM5/27/11
to scala...@googlegroups.com
Agh, I understand exactly!  In fact, I see why this is occurring now as well.  I implemented a 
strict equals method and then in the unit tests added bounds for floating point roundoff.  In experimenting
with Scalacheck, I never added any bounds on the equality check, so the test is failing due
to this floating point roundoff.  This in turns invokes the shinkage functionality of scalacheck and then we
get the length mismatch.

I will read the other thread as well.  Again, I really appreciate your help with this.  Let me know if
you need any help testing new code etc.  

Best of luck,
marc

 
 

Marc

unread,
May 31, 2011, 10:38:10 AM5/31/11
to scala...@googlegroups.com
Again, thanks for all of the help with this.  (Hopefully), one last question:  How would it be best to go about modifying
the generator you specified to take a double generator that generators less extreme values.  I understand how to use
Gen.choose, but not now to integrate that into the code above.


Thanks!
marc

Marc

unread,
May 31, 2011, 5:26:07 PM5/31/11
to scala...@googlegroups.com
I have added this above the definition of the generator.  
implicit def reasonableDouble: Arbitrary[Double] = Arbitrary{Gen.choose(-1e3,1e3)}

Is this the correct way of doing this?
Reply all
Reply to author
Forward
0 new messages