2015-09-19 6 views
5

Gibt es einen Typ-Safe gleich === Implementierung für Scala, die Null Overhead über == hat? Im Gegensatz zu === in Scalaz und ScalaUtils, eine Implementierung, die ein direktes Makro verwendet, um die Überprüfung durchzuführen?Typ-Safe ist gleich Makro?

Ich möchte an vielen Orten === verwenden, aber diese sind Hot-Spots, so dass ich keine zusätzlichen Laufzeitkosten (wie das Erstellen von Klassen und dergleichen) haben möchte.

+1

Was würde eine Makroimplementierung über eine implizite Wertklasse kaufen? –

+0

@TravisBrown-Wertklassen speichern die Zuweisungskosten, aber speichern die Umwegkosten nicht, wenn ich mich nicht irre. Dies könnte relevant sein: http://typelevel.org/blog/2015/08/06/machinist.html –

Antwort

1

Ich denke, Sie können es leicht mit machinist erreichen.

Die Readme auf GitHub gibt genau das === Beispiel:

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

dann können Sie === mit Null-Overhead (ohne zusätzliche Zuweisungen, keine zusätzlichen Indirektion) über ==


verwenden Wenn Sie Auf der Suche nach einer Out-of-the-Box-Implementierung, spire (von dem Maschinist stammt) bietet eins.

Auch cats bietet eins.

Sie sind beide Makro-basierte, wie sie machinist für die Implementierung verwenden.

+0

Die Implementierungen in Kirchturm und Katzen erfordern beide Klassen-Klassen-Instanzen, also müssen Sie darauf achten, dass Sie ' re instantiating diejenigen bei jeder Verwendung usw. –

+0

@TravisBrown, nicht sicher, ich folge. Solange Sie 'ev' nicht aus' EqOps' verwenden, sollten Sie in Ordnung sein, da die Typklasse nur als Beleg für die Verfügbarkeit einer Gleichheitsoperation verwendet wird. Liege ich falsch? –

0

Die Antwort auf Machinist ist wahrscheinlich am besten. Hier ist eine weitere Variante, die hackish Fälle wie Folgern Any oder AnyRef oder die typische Mischung aus zwei unabhängigen Fallklassen (Product with Serializable) erkennt:

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

Das funktioniert nicht mit höheren kinded Typen, noch so Tupeln sind bewusst versagt, während leider Some(1) === Some("hello") kompiliert.


bearbeiten: Ein eingebautes a small library, die höher-kinded Typen unterstützen auf diese verbessert.