如何开发宏来短路零?

时间:2013-04-02 22:39:38

标签: scala macros

在Scala中,如果我有

hub = myBicycle.getFrontWheel.getHub()

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

我目前要做的事情

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

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

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

3 个答案:

答案 0 :(得分:5)

实际上,我最近写的是一个受a question about null-safe dereferences in Scala启发的宏。顺便说一下,这是与此问题非常类似的问题,并且包含了一个关于你可以做些什么来实现这一目标的长期讨论,包括Option的使用,捕捉NPE的奇特方法等。

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

  • 它会返回Option,当某个时候出现空取消引用时,它会为None
  • 它使用if-else将每个成员访问点转换为空值保护访问,当前缀为None时返回null
  • 我觉得它有点复杂......
  • 有一些不起作用的极端情况,我所知道的就是使用getClass方法 - 这是由编译器专门针对其返回类型处理的。
  • 隐式转化可能存在不一致之处。想象一下,你有一个表达式a.b,并且b是通过隐式转换达到的,所以有效地说,这个表达式就像conv(a).b。现在,问题出现了:我们应该检查anull还是conv(a)null还是两者都是?目前,我的宏仅检查anull,因为它对我来说似乎更自然,但在某些情况下这可能不是理想的行为。此外,在我的宏中检测隐式转换是一种小问题,如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"

答案 1 :(得分:4)

除非您必须与固定Java代码进行互操作,否则应使用Option代替null;因此getFrontWheel()将返回Option[Wheel],然后您可以使用map / flatMap向下链接:

val hub:Option[Hub] = myBicycle.getFrontWheel().flatMap(wheel => wheel.getHub())
val spoke:Option[Spoke] = myBicycle.getFrontWheel().flatMap(wheel => wheel.getHub().map(hub => hub.getSpoke()))

(假设hub.getSpoke()返回Spoke,而不是Option[Spoke]

最后一个例子可以改写为

val spoke:Option[Spoke] =
  for (wheel <- myBicycle.getFrontWheel();
       hub <- wheel.getHub())
  yield hub.getSpoke()

如果你真的必须处理null,你可以通过包装Option轻松转换结果:

val wheel:Option[Wheel] = Option(myBicycle.getFrontWheel())

答案 2 :(得分:0)

你可以这样写:

def nil[B >: Null] (fun : => B) : B = {
  try fun
  catch {case e : NullPointerException => null}
}

然后像这样使用它:

val hub = nil(myBicycle.getFrontWheel.getHub)

它也会影响嵌套调用的空异常。如果你想按照前面提到的方式写它,至少要这样做:

val hub = Option(myBicycle.getFrontWheel()).map(_.getHub).getOrElse(null)