Reputation: 648
In Scala, if I have
hub = myBicycle.getFrontWheel.getHub()
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?
I'm currently having to do
hub = if (myBicycle.getFrontWheel() == null) null else myBicycle.getFrontWheel.getHub()
and it gets worse when the chain of accessors is even longer.
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?
Upvotes: 4
Views: 791
Reputation: 10764
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):
Option
, and it will be None
when at some point there was a null dereference.None
when the prefix is null
.getClass
method - which is handled specially by the compiler in regards of its return type.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.The macro:
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)))
}
EDIT
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"
Upvotes: 5
Reputation: 983
You can write something like this:
def nil[B >: Null] (fun : => B) : B = {
try fun
catch {case e : NullPointerException => null}
}
Then use it like this:
val hub = nil(myBicycle.getFrontWheel.getHub)
It will affect null exceptions from nested calls too though. And if you want to write it the way you previously mentioned, do it at least like this:
val hub = Option(myBicycle.getFrontWheel()).map(_.getHub).getOrElse(null)
Upvotes: 0
Reputation: 3696
Unless you're having to interoperate with fixed Java code, you should use Option
instead of null
; thus getFrontWheel()
would return Option[Wheel]
, then you can use map
/flatMap
to go down the chain:
val hub:Option[Hub] = myBicycle.getFrontWheel().flatMap(wheel => wheel.getHub())
val spoke:Option[Spoke] = myBicycle.getFrontWheel().flatMap(wheel => wheel.getHub().map(hub => hub.getSpoke()))
(assumes hub.getSpoke()
returns Spoke
, not Option[Spoke]
)
The last example can be rewritten as
val spoke:Option[Spoke] =
for (wheel <- myBicycle.getFrontWheel();
hub <- wheel.getHub())
yield hub.getSpoke()
If you really must deal with null
you can easily convert the result by wrapping in Option
:
val wheel:Option[Wheel] = Option(myBicycle.getFrontWheel())
Upvotes: 4