0

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?

Patrik
  • 255
  • 3
  • 11

1 Answers1

0

Alright I actually solved this!

class VecDoubleEquality[V[Double] <: Vec[Double, V]] extends Equality[V[Double]] {
  override def areEqual(a: V[Double], b: Any): Boolean = (a,b) match {
    case (lhs: Vec[Double, V], rhs: Vec[Double, V]) => lhs.elems.zip(rhs.elems).forall {case (e1, e2) => e1 === e2 +- 0.1d }
    case _ => false
  }
}
implicit def doubleEq[V[Double] <: Vec[Double, V]] = new VecDoubleEquality[V]

This will correctly give me the right type signature with the recursive type without having to bother with the forSome existential bit.

Patrik
  • 255
  • 3
  • 11