如何开发宏以使null短路? [英] How to develop macro to short-circuit null?

查看:59
本文介绍了如何开发宏以使null短路?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Scala中,如果我有

In Scala, if I have

hub = myBicycle.getFrontWheel.getHub()

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

and it is possible the front wheel is missing, i.e. myBicycle.getFrontWheel() == null, and I just want hub to be assigned null in such a case, what is the most concise way to express that?

我当前必须做

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

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

and it gets worse when the chain of accessors is even longer.

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

Not being familiar with Scala macros, I'm wondering if it's possible to write a Scala macro that somehow captures the method name and applies it only if the object reference is non-null?

推荐答案

实际上,我最近正是受

Actually, I have recently written exactly such a macro, inspired by a question about null-safe dereferences in Scala. By the way, this is very similar question to this one and contains a long discussion about what you can do to achieve this, including usage of Option, fancy ways of catching NPEs and such.

关于我所写的宏的一些评论(包括以下内容):

A few remarks about macro that I have written (source included below):

  • It returns an Option, and it will be None when at some point there was a null dereference.
  • It translates every member access by dot into a null-guarded access using if-else that returns None when the prefix is null.
  • It got a little more complicated that I imagined it to be...
  • There are some corner cases for which it will not work, the one I know about is when using getClass method - which is handled specially by the compiler in regards of its return type.
  • There is a potential inconsistency regarding implicit conversions. Imagine that you have an expression a.b and b is reached through an implicit conversion, so effectively, this expression is something like conv(a).b. Now, the question arises: Should we check if a is null or conv(a) is null or both? Currently, my macro checks only if a is null as it seemed a little more natural to me, but this may not be desired behaviour in some cases. Also, detection of an implicit conversion in my macro is a little hack, described in 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)))
}

编辑

这是使语法更好的另一段代码:

Here's an additional piece of code that makes the syntax nicer:

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]
}

最后,您可以拥有如下代码:

Finally, you can have code like this:

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"

这篇关于如何开发宏以使null短路?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆