事實上,我最近寫的正是這樣一個宏,通過a question about null-safe dereferences in Scala啓發。順便說一句,這是非常類似的問題這一個,包含有關可以做,以實現這一目標,包括Option
使用,醒目的NPE和這樣的幻想方式是什麼進行了長時間討論。
有關宏的一些言論,我已經寫了(包括下面的源):
- 它返回一個
Option
,這將是None
當在某一時刻出現了一個空取消引用。
- 它翻譯由點每一個成員訪問到使用的if-else返回
None
當前綴是null
空森嚴的訪問。
- 它得到多一點複雜,我想像的那麼...
- 有些角落案件不適用,我知道的一個案例是使用
getClass
方法 - 編譯器特別處理它的返回類型。
- 有關隱式轉換的潛在不一致性。想象一下,你有一個表達式
a.b
和b
是通過隱式轉換來實現的,所以有效地,這個表達式類似於conv(a).b
。現在,問題出現了:我們是否應該檢查a
是null
還是conv(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"
你沒有一個無效的「if」在哪裏?所以當它評估爲空時就像是假?然後,如果(some.stuff.is sane())可以寫出,並且不擔心如果some.stuff計算爲null,它將被簡化爲「false」 – andreak
@andreak我是對不起,我不確定你在問什麼。該宏通過將每個'prefix.member'轉換爲'{val p = prefix; if(p!= null)一些(p.member)else None}'。 – ghik