2015-09-19 21 views
5

Czy istnieje bezpieczna wersja równa === implementacji dla Scala, która ma zerowy narzut ponad ==? To znaczy, w przeciwieństwie do === w Scalaz i ScalaUtils, implementacji, która używa prostego makra do wykonania kontroli?Bezpieczne jest równe makro?

Chciałbym użyć === w wielu miejscach, ale są to hot-spoty, więc nie chcę, aby poniosły one dodatkowe koszty runtime (jak konstruowanie klas typu i takie).

+1

Co zaimplementowałaby implementacja makra w stosunku do niejawnej klasy wartości? –

+0

@ Klasy wartości TravisBrown zapisują koszt alokacji, ale nie pomijaj kosztu pośredniego, jeśli się nie mylę. To może być istotne: http://typelevel.org/blog/2015/08/06/machinist.html –

Odpowiedz

1

Myślę, że można go łatwo osiągnąć dzięki machinist.

README na GitHub daje dokładnie przykład ===:

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

Następnie można użyć === z zerowym obciążeniu (bez dodatkowych przydziałów, bez dodatkowych zadnie) nad ==


Jeżeli jesteś poszukując gotowej implementacji, spire (z której pochodzi operator) zapewnia taką.

Podaje także jeden cats.

Obie są oparte na makrach, ponieważ używają do implementacji machinist.

+0

Implementacje we wnętrzu iglicy i kotów wymagają instancji klasy typu, więc musisz się upewnić, że " nie tworzysz tych wystąpień przy każdym użyciu, itp. –

+0

@TravisBrown, nie jestem pewien, czy podążam. Dopóki nie użyjesz 'ev' z' EqOps', powinieneś być w porządku, ponieważ klasa typu jest używana tylko jako dowód dostępności operacji równości. Czy się mylę? –

0

Odpowiedź na podstawie Machinist jest prawdopodobnie najlepsza. Oto bardziej hackish wariant, który wykrywa przypadki takie jak wnioskowanie Any lub AnyRef lub typową mieszankę dwóch klas niepowiązanych przypadków (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" 
    } 
} 

ta nie działa z wyższym kinded rodzajów, a jednak tak krotki umyślnie zawodzą, a niestety kompilują się Some(1) === Some("hello").


Edit: Wbudowany a small library że poprawia to wspieranie wyższego kinded typy.