2013-02-27 85 views
7

我有一個模型,它有一些選項字段,其中包含另一個選項字段。例如:另一個選項對象內的Scala選項對象

case class First(second: Option[Second], name: Option[String]) 
case class Second(third: Option[Third], title: Option[String]) 
case class Third(numberOfSmth: Option[Int]) 

我獲得了來自外部的JSON的這個數據,有時該數據可能包含空的,這是這種模型設計的原因。

所以問題是:什麼是獲得最深領域的最佳方式?

First.get.second.get.third.get.numberOfSmth.get 

上面的方法看起來非常難看,如果其中一個對象是None,它可能會導致異常。我正在尋找斯卡拉茨庫,但沒有找到一個更好的方法來做到這一點。

任何想法? 在此先感謝。

+2

只是說明但flatMap woun」下面給出幾次工作。它應該是'First.second.flatMap(_。third.flatMap(_。numberOfSmth))。get'並且仍然可能拋出異常 – korefn 2013-02-27 12:20:28

+0

確實,謝謝。感謝大家的答案,我找到了我想要的東西。 – psisoyev 2013-02-27 12:23:28

回答

14

的解決方案是使用Option.mapOption.flatMap

First.flatMap(_.second.flatMap(_.third.map(_.numberOfSmth))) 

或同等學歷(見更新在這個答案的末尾):

First flatMap(_.second) flatMap(_.third) map(_.numberOfSmth) 

這將返回Option[Int](提供numberOfSmth返回Int)。如果呼叫鏈中的任何選項爲None,結果將爲None,否則將爲Some(count),其中countnumberOfSmth返回的值。

當然這可以變得非常快速醜陋。出於這個原因,scala支持作爲語法糖的解釋。上述可改寫爲:

for { 
    first <- First 
    second <- first .second 
    third <- second.third 
} third.numberOfSmth 

這無疑是更好的(特別是如果你還沒有看慣map/flatMap無處不在,因爲肯定會使用Scala的一段時間後的情況下),併產生準確的引擎蓋下的代碼相同。

欲瞭解更多的背景,你可以檢查此的其他問題:What is Scala's yield?

UPDATE: 感謝奔詹姆斯指出flatMap是聯想。換句話說,x flatMap(y flatMap z)))x flatMap y flatMap z相同。雖然後者通常不會更短,但它具有避免任何嵌套的優點,這更容易遵循。

這裏是在REPL(4種樣式是等效的,與前兩個使用flatMap嵌套,使用另兩個flatMap的扁平鏈)一些例證:

scala> val l = Some(1,Some(2,Some(3,"aze"))) 
l: Some[(Int, Some[(Int, Some[(Int, String)])])] = Some((1,Some((2,Some((3,aze)))))) 
scala> l.flatMap(_._2.flatMap(_._2.map(_._2))) 
res22: Option[String] = Some(aze) 
scala> l flatMap(_._2 flatMap(_._2 map(_._2))) 
res23: Option[String] = Some(aze) 
scala> l flatMap(_._2) flatMap(_._2) map(_._2) 
res24: Option[String] = Some(aze) 
scala> l.flatMap(_._2).flatMap(_._2).map(_._2) 
res25: Option[String] = Some(aze) 
+3

你不需要使用醜陋的嵌套:由於'flatMap'的關聯性,'flatMap(b flatMap c)'相當於'flatMap b flatMap c' – 2013-02-27 12:24:21

+0

謝謝,你說得很對。我通常將它們嵌套在一起,因爲它模仿了mappped/flatMapped的實際結構(在這種情況下,**是**嵌套的)。但確實,它更像扁平的'flatMap'鏈。 – 2013-02-27 12:43:13

+1

請注意,不嵌套flatMaps的缺點是不能在嵌套的lambda表達式中使用lambda參數,所以儘管它在這種情況下工作,但我不會推薦它通用。它也較慢,並創建更多的功能對象。 – 2013-02-28 09:44:36

4

這可以通過鏈接調用來完成,以flatMap

def getN(first: Option[First]): Option[Int] = 
    first flatMap (_.second) flatMap (_.third) flatMap (_.numberOfSmth) 

你也可以用的,理解這一點,但因爲它迫使你命名每一箇中間值,它是更冗長:

def getN(first: Option[First]): Option[Int] = 
    for { 
    f <- first 
    s <- f.second 
    t <- s.third 
    n <- t.numberOfSmth 
    } yield n 
10

沒有必要scalaz:

for { 
    first <- yourFirst 
    second <- f.second 
    third <- second.third 
    number <- third.numberOfSmth 
} yield number 

或者你ç一個使用嵌套flatMaps

0

我認爲這是你的問題,但只是作爲一般參考矯枉過正:

此嵌套訪問問題是由一個概念叫做鏡頭解決。它們提供了一個很好的機制來通過簡單的組合訪問嵌套的數據類型。作爲介紹,你可能想檢查例如this SO answerthis tutorial。在你的情況下使用鏡頭是否合理的問題是,你是否還必須在嵌套選項結構中執行很多更新(注意:更新不是可變意義上的,而是返回一個新的已修改但不可變的實例)。如果沒有鏡頭,這會導致長時間嵌套的案例類copy代碼。如果你根本不需要更新,我會堅持om-nom-nom's suggestion

+2

@Downvoter:你介意解釋downvote嗎?鑑於OP必須更新這種嵌套結構,我認爲應該允許OP指出鏡頭的概念作爲替代解決方案嗎? – bluenote10 2013-02-27 15:23:30