2013-02-06 17 views
9

È possibile applicare la dimensione di uno Vector a un metodo in fase di compilazione? Voglio modellare uno spazio n-dimensionale euclideo utilizzando una raccolta di punti nello spazio che sembra qualcosa di simile (questo è quello che ho adesso):Scala - Imposizione della dimensione del vettore al momento della compilazione

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

Se ho una coordinata che viene creato tramite EuclideanPoint(Vector(1, 0, 0)), è un punto euclideo in 3D. Detto questo, voglio assicurarmi che il punto di destinazione passato in una chiamata a distanceTo sia della stessa dimensione.

so che posso farlo utilizzando Tuple1-Tuple22, ma voglio rappresentare molti spazi geometrici diversi e avrei scritto 22 classi per ogni spazio, se l'ho fatto con Tuple s - c'è un modo migliore?

+1

Non riesco a capire che è un _answer_, ma potrebbe essere considerato un'idea ... La prima cosa che mi è venuta in mente è combinare una classe Value (nuova in 2.10) con tipi path-dependent per ottenere un tipo che rappresenta il numero intero s specifico. Non ho idea reale se questo potrebbe essere fatto funzionare. Potrei fare un tentativo quando il lavoro è finito per il giorno ... Vedi SIP 15: http://docs.scala-lang.org/overviews/core/value-classes.html –

+0

Questo tipo di vincolo può essere codificato con "programmazione a livello di carattere". Vedi, per esempio, la [serie di blog di Apocalisp] (http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/), e in particolare HList. –

risposta

11

È possibile farlo in un certo numero di modi che sembrano tutti più o meno simili a ciò che Randall Schulz ha descritto in un commento. Il Shapeless library fornisce una particolarmente conveniente implementazione, che consente di ottenere qualcosa di abbastanza vicino a quello che si vuole in questo modo:

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

Ora è possibile scrivere il seguente:

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

E:

scala> orig2d distanceTo unit2d 
res0: Double = 1.4142135623730951 

scala> orig3d distanceTo unit3d 
res1: Double = 1.7320508075688772 

Ma non:

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

Sized viene fornito con una serie di caratteristiche interessanti, tra cui una manciata di operazioni di raccolta che portano con sé le garanzie statiche sulla lunghezza. Possiamo scrivere il seguente esempio:

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

E avere un punto vecchio ordinario nello spazio tridimensionale.

+0

È fantastico, grazie! Domanda veloce - è possibile trasformare un 'IndexedSeq [Double]' nella versione 'Sized'? Sto scavando nel codice e sto avendo problemi a capirlo. – adelbertc

+0

Domanda: Shapeless richiede la versione 2.11 di anteprima/istantanea/nascente? Perchè quando l'ho recuperato e costruito, ha usato uno scatto istantaneo 2.11. (Inoltre, non viene compilato, ma non sono sicuro di cosa si tratti ...) –

+0

No, è possibile ottenere la versione 1.2.3 di Shapeless per 2.10.0 (o 2.9.2), tramite SBT o Maven dipendenza, o verificando il tag 'shapeless-1.2.3' e costruendo quello. –

Problemi correlati