scala - 如何开发短路空的宏?

标签 scala macros

在 Scala 中,如果我有

hub = myBicycle.getFrontWheel.getHub()

并且前轮可能丢失,即 myBicycle.getFrontWheel() == null,我只想为 hub 分配 null 在这种情况下,最简洁的表达方式是什么?

我现在要做

hub = if (myBicycle.getFrontWheel() == null) null else myBicycle.getFrontWheel.getHub()

当访问器链更长时,情况会变得更糟。

由于不熟悉 Scala 宏,我想知道是否可以编写一个 Scala 宏,以某种方式捕获方法名称并仅在对象引用非空时应用它?

最佳答案

实际上,我最近写了一个这样的宏,灵感来自 a question about null-safe dereferences in Scala 。顺便说一句,这个问题与这个问题非常相似,并且包含了关于如何实现这一目标的长篇讨论,包括使用 Option、捕获 NPE 的奇特方法等。

关于我写的宏的一些评论(来源如下):

  • 它返回一个Option,当在某个时刻存在空取消引用时,它将为None
  • 它将每个点成员访问转换为使用 if-else 的空保护访问,当前缀为 null 时,返回 None
  • 事情比我想象的要复杂一些......
  • 在某些极端情况下它不起作用,我所知道的一个是使用 getClass 方法时 - 该方法由编译器根据其返回类型进行特殊处理。
  • 隐式转换可能存在不一致的情况。想象一下,您有一个表达式 a.b ,并且 b 是通过隐式转换实现的,因此实际上,该表达式类似于 conv(a).b。现在,问题出现了:我们是否应该检查 a 是否为 nullconv(a)null 还是两者都存在?目前,我的宏仅检查 a 是否为 null,因为这对我来说似乎更自然,但在某些情况下这可能不是所需的行为。另外,在我的宏中检测隐式转换是一个小技巧,如 this question 中所述。 .

宏:

def withNullGuards[T](expr: T): Option[T] = macro withNullGuards_impl[T]

def withNullGuards_impl[T](c: Context)(expr: c.Expr[T]): c.Expr[Option[T]] = {
  import c.universe._

  def eqOp = newTermName("==").encodedName
  def nullTree = c.literalNull.tree
  def noneTree = reify(None).tree
  def someApplyTree = Select(reify(Some).tree, newTermName("apply"))

  def wrapInSome(tree: Tree) = Apply(someApplyTree, List(tree))

  def canBeNull(tree: Tree) = {
    val sym = tree.symbol
    val tpe = tree.tpe

    sym != null &&
      !sym.isModule && !sym.isModuleClass &&
      !sym.isPackage && !sym.isPackageClass &&
      !(tpe <:< typeOf[AnyVal])
  }

  def isInferredImplicitConversion(apply: Tree, fun: Tree, arg: Tree) =
    fun.symbol.isImplicit && (!apply.pos.isDefined || apply.pos == arg.pos)

  def nullGuarded(originalPrefix: Tree, prefixTree: Tree, whenNonNull: Tree => Tree): Tree =
    if (canBeNull(originalPrefix)) {
      val prefixVal = c.fresh()
      Block(
        ValDef(Modifiers(), prefixVal, TypeTree(null), prefixTree),
        If(
          Apply(Select(Ident(prefixVal), eqOp), List(nullTree)),
          noneTree,
          whenNonNull(Ident(prefixVal))
        )
      )
    } else whenNonNull(prefixTree)

  def addNullGuards(tree: Tree, whenNonNull: Tree => Tree): Tree = tree match {
    case Select(qualifier, name) =>
      addNullGuards(qualifier, guardedQualifier =>
        nullGuarded(qualifier, guardedQualifier, prefix => whenNonNull(Select(prefix, name))))
    case Apply(fun, List(arg)) if (isInferredImplicitConversion(tree, fun, arg)) =>
      addNullGuards(arg, guardedArg =>
        nullGuarded(arg, guardedArg, prefix => whenNonNull(Apply(fun, List(prefix)))))
    case Apply(Select(qualifier, name), args) =>
      addNullGuards(qualifier, guardedQualifier =>
        nullGuarded(qualifier, guardedQualifier, prefix => whenNonNull(Apply(Select(prefix, name), args))))
    case Apply(fun, args) =>
      addNullGuards(fun, guardedFun => whenNonNull(Apply(guardedFun, args)))
    case _ => whenNonNull(tree)
  }

  c.Expr[Option[T]](addNullGuards(expr.tree, tree => wrapInSome(tree)))
}

编辑

这里有一段额外的代码,可以使语法更好:

def any2question_impl[T, R >: T](c: Context {type PrefixType = any2question[T]})(default: c.Expr[R]): c.Expr[R] = {
  import c.universe._

  val Apply(_, List(prefix)) = c.prefix.tree
  val nullGuardedPrefix = withNullGuards_impl(c)(c.Expr[T](prefix))
  reify {
    nullGuardedPrefix.splice.getOrElse(default.splice)
  }
}

implicit class any2question[T](any: T) {
  def ?[R >: T](default: R): R = macro any2question_impl[T, R]
}

最后,您可以使用如下代码:

val str1: String = "hovercraftfullofeels"
val result1 = str1.substring(3).toUpperCase ? "THERE WAS NULL"
println(result1) // prints "ERCRAFTFULLOFEELS"

val str2: String = null
val result2 = str2.substring(3).toUpperCase ? "THERE WAS NULL"
println(result2) // prints "THERE WAS NULL"

关于scala - 如何开发短路空的宏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15775699/

相关文章:

scala - 在宏中使用私有(private)构造函数

Scala Play Guice 依赖注入(inject)失败

excel - 如何使用 scala 解压 zip 文件?

java - 如何在scala中使用flatMap来对一组 "vals"进行分组

powershell - 具有参数/参数的Powershell

通过函数调用覆盖变量赋值的 C 宏

scala - 如何使用 MongoListField 返回列表

scala:包和源目录层次结构之间的关系

macros - 宏是否可以将标识符变为小写?

macros - LibreOffice 4.1 编写器 : macro to adjust column widths in tables