2015-09-19 13 views
5

Esiste un'implementazione di tipo === per Scala che non ha un overhead su ==? Cioè, a differenza di === in Scalaz e ScalaUtils, un'implementazione che utilizza una macro diritta per eseguire il controllo?Macro di tipo sicuro uguale a?

Mi piacerebbe usare === in molti posti, ma questi sono hot-spot, quindi non voglio che ciò comporti costi extra di runtime (come la costruzione di classi di tipi e simili).

+1

Che cosa potrebbe acquistare un'implementazione di macro su una classe di valore implicita? –

+0

@Le classi di valore diTravisBrown salvano il costo di allocazione, ma non salvano il costo di riferimento indiretto, se non sbaglio. Questo potrebbe essere rilevante: http://typelevel.org/blog/2015/08/06/machinist.html –

risposta

1

Penso che si possa ottenere facilmente con machinist.

Il README su GitHub dà esattamente la === esempio:

import scala.{specialized => sp} 

import machinist.DefaultOps 

trait Eq[@sp A] { 
    def eqv(lhs: A, rhs: A): Boolean 
} 

object Eq { 
    implicit val intEq = new Eq[Int] { 
    def eqv(lhs: Int, rhs: Int): Boolean = lhs == rhs 
    } 

    implicit class EqOps[A](x: A)(implicit ev: Eq[A]) { 
    def ===(rhs: A): Boolean = macro DefaultOps.binop[A, Boolean] 
    } 
} 

quindi è possibile utilizzare === da zero in testa (senza assegnazioni di troppo, senza indirezione extra) oltre ==


Se sei alla ricerca di un'implementazione immediata, spire (da cui è stato originato il macchinista) ne fornisce uno.

Anche cats ne fornisce uno.

Sono entrambi basati su macro poiché utilizzano machinist per l'implementazione.

+0

Le implementazioni in spire e gatti richiedono entrambe istanze di classe di tipo, però, quindi dovresti aver cura di te ' Non istanziare quelli su ogni uso, ecc. –

+0

@TravisBrown, non sono sicuro che io segua. Finché non usi 'ev' da' EqOps' dovresti stare bene, dato che la classe del tipo è usata solo come prova della disponibilità di un'operazione di uguaglianza. Ho sbagliato? –

0

La risposta basata su Machinist è probabilmente la migliore. Qui è una variante più hackish che rileva casi come inferire Any o AnyRef o la tipica combinazione di due classi non correlate casi (Product with Serializable):

import scala.collection.breakOut 
import scala.language.experimental.macros 
import scala.reflect.macros.blackbox 

object Implicits { 
    implicit class TripleEquals[A](a: A) { 
    def === [B >: A](b: B): Boolean = macro Macros.equalsImpl[A, B] 
    } 
} 

object Macros { 
    private val positiveList = Set("scala.Boolean", "scala.Int", "scala.Long", 
           "scala.Float", "scala.Double", "scala.Option) 
    private val negativeList = Set("java.lang.Object", "java.io.Serializable", 
           "<refinement>") 

    def equalsImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context) 
                (b: c.Expr[A]): c.Tree = { 
    import c.universe._ 
    val bTpe = weakTypeOf[B] 
    val base = bTpe.baseClasses 
    val names: Set[String] = base.collect { 
     case sym if sym.isClass => sym.fullName 
    } (breakOut) 

    // if a primitive is inferred, we're good. otherwise: 
    if (names.intersect(positiveList).isEmpty) { 
     // exclude all such as scala.Product, scala.Equals 
     val withoutTopLevel = names.filterNot { n => 
     val i = n.lastIndexOf('.') 
     i == 5 && n.startsWith("scala") 
     } 
     // exclude refinements and known Java types 
     val excl = withoutTopLevel.diff(negativeList) 
     if (excl.isEmpty) { 
     c.abort(c.enclosingPosition, s"Inferred type is too generic: `$bTpe`") 
     } 
    } 

    // now simply rewrite as `a == b` 
    val q"$_($a)" = c.prefix.tree 
    q"$a == $b" 
    } 
} 

Questo non funziona con tipi superiori-kinded, ancora, in modo tuple stanno deliberatamente fallendo, mentre sfortunatamente compila Some(1) === Some("hello").


Edit: A built a small library che migliora su questo per supportare i tipi di alto-kinded.