2015-04-23 10 views
5

Ho intenzione di implementare un operatore di confronto concatenato per Scala, ma dopo alcuni tentativi non penso che ci sia un modo per farlo. Ecco come dovrebbe funzionare:Scala - Come "ritardare" la compilation di espressioni

val a = 3 
1 < a < 5 //yields true 
3 < a < 5 //yields false 

Il problema è, scala compilatore è abbastanza avido, mentre la valutazione di espressioni, in modo che le espressioni di cui sopra vengono valutati come segue:

1 < a //yields true 
true < 5 //compilation error 

Ho cercato di scrivere il codice per implementare in qualche modo, e qui è quello che ho provato:

  • conversioni implicite da tipo Int al mio tipo RichComparisonInt - non ha aiutato a causa della valutazione modo nella foto sopra,
  • classe Overriding Int con la mia classe - non si può fare, perché è sia Intabstract e final,
  • Ho cercato di creare case class con nome <, proprio come ::, ma poi ho scoprire , che questa classe viene creata solo per la corrispondenza dei modelli,
  • Ho pensato di creare una conversione implicita da => Boolean, che funzionerebbe sul livello di compilazione, ma non c'è modo di estrarre i parametri dell'operazione, che ha portato al risultato Boolean.

C'è un modo per farlo in Scala? Forse i macro avrebbero potuto fare il lavoro?

+0

'implicit class' per' Boolean' con un metodo '<(y: Int)', che è una macro, e controlla il suo 'prefisso', decostruisce come' x.y (a) ', ed ecco fatto. – sjrd

+1

@sjrd Non penso che funzionerà nel caso generale a causa del piegamento. –

+1

La piegatura avviene solo se 'a' è un' val finale 'senza tipo. Nell'esempio sopra, posso garantire che il compilatore non lo piega (almeno non durante il typer, prima che le macro entrino in azione). – sjrd

risposta

3

Ecco una soluzione che utilizza macros. L'approccio generale qui è quello di arricchire Boolean in modo che abbia un metodo macro che esamina il prefix del contesto per trovare il confronto utilizzato per generare tale Boolean.

Per esempio, supponiamo di avere:

implicit class RichBooleanComparison(val x: Boolean) extends AnyVal { 
    def <(rightConstant: Int): Boolean = macro Compare.ltImpl 
} 

E una definizione macro con metodo intestazione:

def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean] 

Ora supponiamo che il compilatore è l'analisi dell'espressione 1 < 2 < 3. A quanto pare, è possibile utilizzare c.prefix per ottenere l'espressione 1 < 2 durante la valutazione del corpo del metodo macro. Tuttavia, il concetto di constant folding ci impedisce di farlo qui. La piegatura costante è il processo mediante il quale il compilatore calcola le costanti predeterminate in fase di compilazione. Pertanto, quando vengono valutati i macro, lo c.prefix è già stato piegato per essere solo true in questo caso. Abbiamo perso l'espressione 1 < 2 che ha portato a true. Puoi leggere ulteriori informazioni sulla costante piegatura e le loro interazioni con macro Scala su this issue e un po 'su this question.

Se siamo in grado di limitare la portata della discussione ai soli espressioni della forma C1 < x < C2, dove C1 e C2 sono costanti, e x è una variabile, allora questo diventa fattibile, dal momento che questo tipo di espressione non sarà influenzato da piegatura costante. Ecco un'implementazione:

object Compare { 
    def ltImpl(c: Context)(rightConstant: c.Expr[Int]): c.Expr[Boolean] = { 
    import c.universe._ 
    c.prefix.tree match { 
     case Apply(_, Apply(Select([email protected](Constant(_)), _), ([email protected](_, TermName(_))) :: Nil) :: Nil) => 
      val leftConstant = c.Expr[Int](lhs) 
      val variable = c.Expr[Int](x) 
      reify((leftConstant.splice < variable.splice) && (variable.splice < rightConstant.splice)) 

     case _ => c.abort(c.enclosingPosition, s"Invalid format. Must have format c1<x<c2, where c1 and c2 are constants, and x is variable.") 
    } 
    } 
} 

Qui, abbiniamo contesto prefix al tipo previsto, estrarre le parti pertinenti (lhs e x), la costruzione di nuove sottostrutture utilizzando c.Expr[Int], e costruire un nuovo albero piena espressione utilizzando reify e splice per effettuare il confronto a 3 vie desiderato. Se non c'è corrispondenza con il tipo previsto, questo non riuscirà a compilare.

Questo ci permette di fare:

val x = 5 
1 < x < 5 //true 
6 < x < 7 //false 
3 < x < 4 //false 

lo desideri!

Il docs about macros, trees e this presentation sono buone risorse per ulteriori informazioni sulle macro.

+1

Wow. È notevole. Grazie. Fantastica spiegazione –

3

Avrai difficoltà a denominare il metodo < senza utilizzare una macro, poiché il compilatore sceglierà sempre il metodo < che è in realtà su Int, al contrario di qualsiasi classe arricchita. Ma se si può dare che fino, si può arricchire Int come lei ha suggerito, e restituire un tipo di intermediario che tiene traccia del confronto finora:

implicit class RichIntComparison(val x: Int) extends AnyVal { 
    def <<<(y: Int) = Comparison(x < y, y) 
} 

case class Comparison(soFar: Boolean, y: Int) { 
    def <<<(z: Int) = soFar && (y < z) 
} 

Poi possiamo fare:

1 <<< 2 <<< 3 

//Equivalent to: 
val rc: Comparison = RichIntComparison(1).<<<(2) 
rc.<<<(3) 

Se si vuole, si potrebbe anche aggiungere una conversione implicita da Comparison a Boolean in modo che è possibile utilizzare per la metà <<< un confronto così:

object Comparison { 
    implicit def comparisonToBoolean(c: Comparison): Boolean = c.soFar 
} 

che permetterebbe di fare:

val comp1: Boolean = 1 <<< 2 //true 
val comp2: Boolean = 1 <<< 2 <<< 3 //true 

Ora una volta che hai introdotto questo conversione implicita, si può tornare indietro e fare <<< su Comparison ritorno Comparison invece, consente di fare il concatenamento ancora più esteso:

case class Comparison(soFar: Boolean, y: Int) { 
    def <<<(z: Int): Comparison = Comparison(soFar && (y < z), z) 
    //You can also use < for everything after the first comparison: 
    def <(z: Int) = <<<(z) 
} 

//Now, we can chain: 
val x: Boolean = 1 <<< 2 <<< 3 <<< 4 <<< 3 //false 
val x: Boolean = 1 <<< 2 < 3 < 4 < 7 //true 
+0

Questa è una bella soluzione, ma è facile. L'obiettivo principale della mia domanda non era quello di consentire tali confronti. Si trattava più del problema solitario della compilazione di espressioni e dell'estrazione della struttura di espressione. –

+1

@BartekAndrzejczak Ah capisco. Bene, lascerò questo qui nel caso soddisfi le esigenze di qualcun altro. I macro sono sicuramente il modo per andare qui nel tuo caso, quindi. Darei un'occhiata se ne avrò la possibilità più tardi e nessun altro lo raggiungerà. –

Problemi correlati