【问题标题】:Type-safe equals macro?类型安全等于宏?
【发布时间】:2016-04-24 15:51:47
【问题描述】:

Scala 是否有一个类型安全的 equals === 实现,其开销比 == 零开销?也就是说,不像 Scalaz 和 ScalaUtils 中的===,一个使用直接宏来执行检查的实现?

我想在很多地方使用===,但这些都是热点,所以我不希望这会产生任何额外的运行时成本(比如构造类型类等)。

【问题讨论】:

  • 相对于隐式值类,宏实现会给你带来什么?
  • @TravisBrown 值类节省了分配成本,但如果我没记错的话,不要节省间接成本。这可能是相关的:typelevel.org/blog/2015/08/06/machinist.html

标签: scala macros equals


【解决方案1】:

我认为您可以使用machinist 轻松实现。

GitHub 上的 README 提供了 === 示例:

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 来实现。

【讨论】:

  • 在 spire 和 cat 中的实现都需要类型类实例,所以你必须注意不要在每次使用时实例化它们,等等。
  • @TravisBrown,不确定我是否关注。只要您不使用EqOps 中的ev 就可以了,因为类型类仅用作相等操作可用性的证明。我错了吗?
【解决方案2】:

基于机械师的答案可能是最好的。这是一个更骇人听闻的变体,可以检测诸如推断 AnyAnyRef 或两个不相关案例类 (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 对此进行了改进以支持更高种类的类型。

【讨论】:

    猜你喜欢
    • 2018-05-31
    • 1970-01-01
    • 2013-07-31
    • 1970-01-01
    • 1970-01-01
    • 2022-07-02
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    相关资源
    最近更新 更多