2013-04-02 17 views
2

在Scala中,如果我有如何開發宏來短路null?

hub = myBicycle.getFrontWheel.getHub() 

,並有可能在前輪缺失,即myBicycle.getFrontWheel() == null,我只是想hub在這樣的情況下,被分配null,什麼是表達最簡潔的方式那?

我目前不必做

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

,它變得更糟時存取鏈甚至更長。

不熟悉斯卡拉宏,我想知道是否有可能寫Scala的宏,不知怎的,捕獲方法的名字和它適用於僅當對象引用是非空?

回答

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您可以輕鬆地在0123包裹轉換結果:

val wheel:Option[Wheel] = Option(myBicycle.getFrontWheel()) 
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) 
5

事實上,我最近寫的正是這樣一個宏,通過a question about null-safe dereferences in Scala啓發。順便說一句,這是非常類似的問題這一個,包含有關可以做,以實現這一目標,包括Option使用,醒目的NPE和這樣的幻想方式是什麼進行了長時間討論。

有關宏的一些言論,我已經寫了(包括下面的源):

  • 它返回一個Option,這將是None當在某一時刻出現了一個空取消引用。
  • 它翻譯由點每一個成員訪問到使用的if-else返回None當前綴是null空森嚴的訪問。
  • 它得到多一點複雜,我想像的那麼...
  • 有些角落案件不適用,我知道的一個案例是使用getClass方法 - 編譯器特別處理它的返回類型。
  • 有關隱式轉換的潛在不一致性。想象一下,你有一個表達式a.bb是通過隱式轉換來實現的,所以有效地,這個表達式類似於conv(a).b。現在,問題出現了:我們是否應該檢查anull還是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" 
+0

你沒有一個無效的「if」在哪裏?所以當它評估爲空時就像是假?然後,如果(some.stuff.is sane())可以寫出,並且不擔心如果some.stuff計算爲null,它將被簡化爲「false」 – andreak

+0

@andreak我是對不起,我不確定你在問什麼。該宏通過將每個'prefix.member'轉換爲'{val p = prefix; if(p!= null)一些(p.member)else None}'。 – ghik