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 Tuple
s - is there a better way?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With