类型安全等于宏?
Type-safe equals macro?
是否有类型安全的等于 ===
的 Scala 实现,它比 ==
的开销为零?也就是说,与Scalaz和ScalaUtils中的===
不同,一个使用直接宏来执行检查的实现?
我想在很多地方使用 ===
但这些都是热点,所以我不希望它产生任何额外的运行时成本(比如构造类型 类 等) .
我想你可以用 machinist 轻松实现它。
GitHub 上的自述文件给出了 ===
示例:
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]
}
}
然后你可以使用 ===
零开销(没有额外的分配,没有额外的间接)超过 ==
如果您正在寻找开箱即用的实现,spire
(机械师的起源)提供了一个。
也cats
提供了一个。
它们都是基于宏的,因为它们使用 machinist
来实现。
基于机械师的答案可能是最好的。这是一个更 hackish 的变体,它检测诸如推断 Any
或 AnyRef
或两个不相关案例的典型组合 类 (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"
}
}
这不适用于更高种类的类型,因此元组故意失败,而不幸的是 Some(1) === Some("hello")
编译。
编辑:内置 a small library 对此进行了改进以支持更高种类的类型。
是否有类型安全的等于 ===
的 Scala 实现,它比 ==
的开销为零?也就是说,与Scalaz和ScalaUtils中的===
不同,一个使用直接宏来执行检查的实现?
我想在很多地方使用 ===
但这些都是热点,所以我不希望它产生任何额外的运行时成本(比如构造类型 类 等) .
我想你可以用 machinist 轻松实现它。
GitHub 上的自述文件给出了 ===
示例:
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]
}
}
然后你可以使用 ===
零开销(没有额外的分配,没有额外的间接)超过 ==
如果您正在寻找开箱即用的实现,spire
(机械师的起源)提供了一个。
也cats
提供了一个。
它们都是基于宏的,因为它们使用 machinist
来实现。
基于机械师的答案可能是最好的。这是一个更 hackish 的变体,它检测诸如推断 Any
或 AnyRef
或两个不相关案例的典型组合 类 (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"
}
}
这不适用于更高种类的类型,因此元组故意失败,而不幸的是 Some(1) === Some("hello")
编译。
编辑:内置 a small library 对此进行了改进以支持更高种类的类型。