Checking compile time for a vector dimension

I implement several easy math vectors in scala. I would like to use a type system to check vector compatibility at compile time. For example, an attempt to add a vector of dimension 2 to another vector of dimension 3 should lead to a compilation error.

So far, I have defined sizes as case classes:

sealed trait Dim case class One() extends Dim case class Two() extends Dim case class Three() extends Dim case class Four() extends Dim case class Five() extends Dim 

And here is the definition of vectors:

 class Vec[D <: Dim](val values: Vector[Double]) { def apply(i: Int) = values(i) def *(k: Double) = new Vec[D]( values.map(_*k) ) def +(that: Vec[D]) = { val newValues = ( values zip that.values ) map { pair => pair._1 + pair._2 } new Vec[D](newValues) } override lazy val toString = "Vec(" + values.mkString(", ") + ")" } 

This solution works well, however I have two problems:

  • How to add a dimension():Int method that returns a dimension (ie 3 for a Vec[Three] )?

  • How can I handle higher dimensions without first declaring all required case classes?

PS: I know there are good existing math vector libraries, I'm just trying to improve my understanding of scala.

+1
source share
3 answers

My suggestions:

+3
source

I suggest something like this:

 sealed abstract class Dim(val dimension:Int) object Dim { class One extends Dim(1) class Two extends Dim(2) class Three extends Dim(3) implicit object One extends One implicit object Two extends Two implicit object Three extends Three } case class Vec[D <: Dim](values: Vector[Double])(implicit dim:D) { require(values.size == dim.dimension) def apply(i: Int) = values(i) def *(k: Double) = Vec[D]( values.map(_*k) ) def +(that: Vec[D]) = Vec[D]( ( values zip that.values ) map { pair => pair._1 + pair._2 }) override lazy val toString = values.mkString("Vec(",", ",")") } 

Of course, you can only get time checks on the length of the vector this way, but, as others have already pointed out, you need something like elementary numbers or other font-level programming methods to achieve compile-time checks.

  import Dim._ val a = Vec[Two](Vector(1.0,2.0)) val b = Vec[Two](Vector(1.0,3.0)) println(a + b) //--> Vec(2.0, 5.0) val c = Vec[Three](Vector(1.0,3.0)) //--> Exception in thread "main" java.lang.ExceptionInInitializerError //--> at scalatest.vecTest.main(vecTest.scala) //--> Caused by: java.lang.IllegalArgumentException: requirement failed 
+1
source

If you don't want to go down the Peano route, you can always build Vec with D , and then use the instance to define the dimension using the Dim companion object. For instance:

 object Dim { def dimensionOf(d : Dim) = d match { case One => 1 case Two => 2 case Three => 3 } } sealed trait Dim 

I think you should use case objects to select, not case classes:

 case object One extends Dim case object Two extends Dim 

Then on your vector, you may need to actually save the Dim:

 object Vec { def vec1 = new Vec[One](One) def vec2 = new Vec[Two](Two) def vec3 = new Vec[Three](Three) } class Vec[D <: Dim](d : D) { def dimension : Int = Dim dimensionOf d //etc 
0
source

All Articles