Been playing around with scalas type system and again i find myself fighting it. I have created a Vector library for some simple graphics applications and im fairly happy with it.
Now i want to be able to test the Vector properties with scalacheck and scalatest and so far so good. The problem im facing now is that checking vector equality when using doubles or floats are not as simple as i thought. Now I figured that the most idiomatic way in the test framework is to create a new Equality
for my vector classes. But let's step back and take a look at my vector defenitions
abstract class Vec[T, V[T] <: Vec[T, V]](val elems: T*)(implicit num: VecIntegral[T], factory: VecFactory[V])
class Vec2[T](x: T, y: T)(implicit num: VecIntegral[T]) extends Vec[T, Vec2](x,y)
Vec3[T](x: T, y: T, z: T)(implicit num: VecIntegral[T]) extends Vec[T, Vec3](x,y, z)
Also not shown here unapply and apply are defined for the Vec object allowing for pattern matching and the like.
So now my first attempt for writing tests looks something like this
abstract class VecSuite[T: Arbitrary, V[T] <: Vec[T, V]](implicit genVec: Arbitrary[V[T]], num: VecIntegral[T])
extends PropSpec with PropertyChecks {
import num._
property("associative add") {
forAll { (a: V[T], b: V[T]) =>
assert((a + b).===(b + a))
}
}
property("scalar distributed") {
forAll { (a: V[T], b: V[T], s: T) =>
assert((a + b) * s === a * s + b * s)
}
}
...
}
class Vec2IntSuite extends VecSuite[Int, Vec2]
class Vec2FloatSuite extends VecSuite[Float, Vec2]
class Vec2DoubleSuite extends VecSuite[Double, Vec2]
class Vec2LongSuite extends VecSuite[Long, Vec2]
Again here i dont show but i have implicit factories implemented for Vec2[T]
and Vec3[T]
classes.
So this works really well. I write general tests and can apply them to all my different supported Vector implementations, except that for Float
and Double
i get rounding errors and my equality checks blows up.
So now I start playing around with the Equality
class trying to make it general between Vec2
and Vec2
so that i only need two implicit values for Double
and Float
an attempt:
implicit val doubleEq = new Equality[ V[Double] forSome{ type V[Double] <: Vec[Double, V] }] {
override def areEqual(a: V[Double] forSome {type V[Double] <: Vec[Double, V]}, b: Any): Boolean = (a,b) match {
case (lhs: Vec[Double, _], rhs: Vec[Double, _]) => lhs.elems.zip(rhs.elems).forall {case (e1, e2) => e1 === e2 +- 0.01d }
case _ => false
}
}
But this does not sit well with the compiler and it blows up with a java.lang.StackOverflowException
.
Is there any way of writing the type of the Equality
so that it will implicitly be used in my testcases irregardless if it is Vec2
or Vec3
as long as it is of type Double
?