2016-09-06 3 views
0

Herumspielen mit Scalas type System und wieder finde ich mich, es kämpfend. Ich habe eine Vektorbibliothek für einige einfache Grafikanwendungen erstellt und bin ziemlich glücklich damit.scala höhere Arten und scalatical Equality

Jetzt möchte ich in der Lage sein, die Vektoreigenschaften mit scalacheck und scalatest zu testen und so weit so gut. Das Problem, dem wir jetzt gegenüberstehen, ist, dass die Überprüfung der Vektorgleichheit bei der Verwendung von Doubles oder Floats nicht so einfach ist, wie ich dachte. Nun stellte ich fest, dass der idiomatische Weg im Testframework darin besteht, einen new Equality für meine Vektorklassen zu erstellen. Aber lassen Sie uns einen Schritt zurück und werfen Sie einen Blick auf meine Vektor 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) 

auch hier nicht Unapply gezeigt und gelten für die Vec Objekt definiert unter Berücksichtigung Musterabgleich und dergleichen.

So jetzt mein erster Versuch zum Schreiben von Tests sieht so etwas wie dieses

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] 

Wieder Hier zeige ich nicht, aber ich habe implizit für Vec2[T] und Vec3[T] Klassen implementiert Fabriken.

Das funktioniert also wirklich gut. Ich schreibe allgemeine Tests und kann sie auf alle meine verschiedenen unterstützten Vektorimplementierungen anwenden, außer dass ich für Float und Double Rundungsfehler bekomme und meine Gleichheitsprüfungen explodiert.

So, jetzt fange ich das Spiel mit der Equality Klasse versuchen, es zu machen allgemeinen zwischen Vec2 und Vec2, so dass ich nur noch zwei implizite Werte für Double und Float ein Versuch:

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 
    } 
} 

Aber das sitzt nicht gut mit dem Compiler und es explodiert mit einem java.lang.StackOverflowException.

Gibt es eine Möglichkeit, die Art der Equality des Schreibens, so dass es implizit in meinem Testfall verwendet wird Irregardless wenn es Vec2 oder Vec3 ist, solange es vom Typ Double?

Antwort

0

Ok, ich habe das gelöst!

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] 

Das gibt mir richtig den richtige Art Signatur mit dem rekursiven Typ, ohne mit dem forSome existentiellen Bit kümmern zu müssen.

Verwandte Themen