2013-02-06 5 views
9

Ist es möglich, die Größe einer Vector zu einer Methode zur Kompilierzeit übergeben zu erzwingen? Ich möchte eine Sammlung von Punkten in dem Raum einen n-dimensionale euklidischen Raum modellieren verwenden, die etwa wie folgt aussieht (das ist, was ich habe jetzt):Scala - Erzwingen der Größe von Vektor zur Kompilierzeit

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

Wenn ich eine Koordinate, die über EuclideanPoint(Vector(1, 0, 0)) erstellt werden, es ist ein 3D Euklidischer Punkt. In Anbetracht dessen möchte ich sicherstellen, dass der Zielpunkt, der in einem Aufruf an distanceTo übergeben wird, von derselben Dimension ist.

Ich weiß, ich kann dies tun, indem Tuple1-Tuple22, aber ich möchte viele verschiedene geometrische Räume darzustellen, und ich würde 22 Klassen für jeden Raum schreiben, wenn ich es mit Tuple s hat - gibt es einen besseren Weg?

+1

Ich kann nicht in gutem Bewusstsein dies zu einer _answer_, aber es könnte als eine Idee qualifizieren ... Das erste, was mir eingefallen ist, eine Value Class (neu in 2.10) mit Pfad-abhängigen Typen zu bekommen ein Typ, der eine bestimmte ganze Zahl darstellt. Ich habe keine Ahnung, ob das funktionieren könnte. Ich könnte es versuchen, wenn die Arbeit für den Tag vorbei ist ... Siehe SIP 15: http://docs.scala-lang.org/overviews/core/value-classes.html –

+0

Diese Art von Constraint kann mit codiert werden "Typ-Level-Programmierung". Siehe zum Beispiel die [Apocalyp-Blogserie] (http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/) und insbesondere HList. –

Antwort

11

Es ist möglich, dies in einer Reihe von Möglichkeiten zu tun, die mehr oder weniger wie das aussehen, was Randall Schulz in einem Kommentar beschrieben hat. Die Shapeless library bietet eine besonders komfortable Implementierung, die Sie etwas ziemlich nahe kommen können, was Sie so wollen:

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

Jetzt können Sie schreiben folgendes:

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

Und:

scala> orig2d distanceTo unit2d 
res0: Double = 1.4142135623730951 

scala> orig3d distanceTo unit3d 
res1: Double = 1.7320508075688772 

Aber nicht:

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

Sized kommt mit einer Reihe von netten Funktionen, einschließlich einer Handvoll Sammlungen Operationen, die statische Garantien über die Länge tragen. Wir können folgendes zum Beispiel schreiben:

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

Und haben Sie einen gewöhnlichen alten Punkt im dreidimensionalen Raum.

+0

Das ist großartig, danke! Schnelle Frage - ist es möglich, eine 'IndexedSeq [Double]' in die 'Sized' Version zu verwandeln? Ich blättere den Code durch und habe Probleme, es herauszufinden. – adelbertc

+0

Frage: Benötigt Shapeless die Vorabversion/Snapshot/Scala 2.11? Weil, als ich es zurückholte und es baute, benutzte es einen 2.11 Schnappschuß Scala. (Auch kompiliert es nicht, aber ich bin unsicher, worum es geht ...) –

+0

Nein, Sie können Version 1.2.3 von Shapeless für 2.10.0 (oder 2.9.2) entweder über eine SBT oder Maven erhalten Abhängigkeit, oder indem Sie das 'shapeless-1.2.3'-Tag auschecken und das erstellen. –