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.
Was würde eine Makroimplementierung über eine implizite Wertklasse kaufen? –
@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 –