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.
'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
@sjrd Non penso che funzionerà nel caso generale a causa del piegamento. –
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