Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala - Enforcing size of Vector at compile time

Is it possible to enforce the size of a Vector passed in to a method at compile time? I want to model an n-dimensional Euclidean space using a collection of points in the space that looks something like this (this is what I have now):

case class EuclideanPoint(coordinates: Vector[Double]) {
  def distanceTo(desination: EuclieanPoint): Double = ???
}

If I have a coordinate that is created via EuclideanPoint(Vector(1, 0, 0)), it is a 3D Euclidean point. Given that, I want to make sure the destination point passed in a call to distanceTo is of the same dimension.

I know I can do this by using Tuple1 to Tuple22, but I want to represent many different geometric spaces and I would be writing 22 classes for each space if I did it with Tuples - is there a better way?

like image 370
adelbertc Avatar asked Feb 06 '13 23:02

adelbertc


2 Answers

It is possible to do this in a number of ways that all look more or less like what Randall Schulz has described in a comment. The Shapeless library provides a particularly convenient implementation, which lets you get something pretty close to what you want like this:

import shapeless._

case class EuclideanPoint[N <: Nat](
   coordinates: Sized[IndexedSeq[Double], N] { type A = Double }
) {
  def distanceTo(destination: EuclideanPoint[N]): Double = 
    math.sqrt(
      (this.coordinates zip destination.coordinates).map {
        case (a, b) => (a - b) * (a - b)
      }.sum
    )
}

Now you can write the following:

val orig2d = EuclideanPoint(Sized(0.0, 0.0))
val unit2d = EuclideanPoint(Sized(1.0, 1.0))

val orig3d = EuclideanPoint(Sized(0.0, 0.0, 0.0))
val unit3d = EuclideanPoint(Sized(1.0, 1.0, 1.0))

And:

scala> orig2d distanceTo unit2d
res0: Double = 1.4142135623730951

scala> orig3d distanceTo unit3d
res1: Double = 1.7320508075688772

But not:

scala> orig2d distanceTo unit3d
<console>:15: error: type mismatch;
 found   : EuclideanPoint[shapeless.Nat._3]
 required: EuclideanPoint[shapeless.Nat._2]
              orig2d distanceTo unit3d
                                ^

Sized comes with a number of nice features, including a handful of collections operations that carry along static guarantees about length. We can write the following for example:

val somewhere = EuclideanPoint(Sized(0.0) ++ Sized(1.0, 0.0))

And have an ordinary old point in three-dimensional space.

like image 128
Travis Brown Avatar answered Oct 24 '22 03:10

Travis Brown


You could do something your self by doing a type level encoding of the Natural Numbers like: http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/. Then just parametrizing your Vector by a Natural. Would not require the extra dependency, but would probably be more complicated then using Shapeless.

like image 2
jroesch Avatar answered Oct 24 '22 03:10

jroesch