2016-10-16 85 views
2

當我有多個選項,我要處理的東西,只有當所有的人都有一個值,該for comprehension提供了一個很好的方式來編寫代碼多個選項處理和記錄沒有找到的情況

for { 
    a <- aOption 
    b <- bOption 
    c <- cOption 
    d <- dOption 
} yield {...process...} 

雖然這是非常有用和優雅和簡潔的編寫代碼的方式,我錯過了記錄的能力,如果說「cOption」沒有價值,因此處理沒有發生。

在上面的代碼中是否有一個不錯的方法,能夠記錄缺少的值而不是通過嵌套的ifs。

回答

3

你可以寫一個簡單的功能,但它會記錄只內Option第一缺席值(由於for-comprehension順序性質):

def logEmpty[T](opt: Option[T], msgIfNone: String) = { 
    if (opt.isEmpty) println(msgIfNone) //or something like logger.warn 
    opt 
} 

用法:

for { 
    a <- logEmpty(aOption, "Sorry no a") 
    b <- logEmpty(bOption, "Sorry no b") 
    c <- logEmpty(cOption, "Sorry no c") 
    d <- logEmpty(dOption, "Sorry no d") 
} yield {...process...} 

DSL樣:

implicit class LogEmpty[T](opt: Option[T]) { 
    def reportEmpty(msg: String) = { 
    if (opt.isEmpty) println(msg) 
    opt 
    } 
} 

用法:

for { 
    a <- aOption reportEmpty "Sorry no a" 
    b <- bOption reportEmpty "Sorry no b" 
    c <- cOption reportEmpty "Sorry no c" 
    d <- dOption reportEmpty "Sorry no d" 
} yield {a + b + c + d} 

例子:

scala> for { 
    | a <- Some("a") reportEmpty "Sorry no a" 
    | b <- None reportEmpty "Sorry no b" 
    | c <- Some("c") reportEmpty "Sorry no c" 
    | d <- None reportEmpty "Sorry no d" 
    | } yield {a + b + c + d} 
Sorry no b 
res19: Option[String] = None 

如果您需要報告更多 - 最好的辦法是使用Validation從scalaz或catsValidated,所以你對abscence消息會被表示爲無效狀態Validated。您始終可以將Validated轉換爲Option

解決方案:

import cats._ 
import cats.data.Validated 
import cats.data.Validated._ 
import cats.implicits._ 

implicit class RichOption[T](opt: Option[T]) { 
    def validOr(msg: String) = 
    opt.map(Valid(_)).getOrElse(Invalid(msg)).toValidatedNel 

} 

例子:

val aOption = Some("a") 
val bOption: Option[String] = None 
val cOption: Option[String] = None 

scala> aOption.validOr("no a") |+| bOption.validOr("no b") |+| cOption.validOr("no c") 
res12: cats.data.Validated[cats.data.NonEmptyList[String],String] = Invalid(NonEmptyList(no b, no c)) 

scala> aOption.validateOr("no a") |+| aOption.validateOr("no a again") 
res13: cats.data.Validated[cats.data.NonEmptyList[String],String] = Valid(aa) 

我用|+|操作假設串聯,但你可以使用應用性製造商(或只是zip),以及爲了在實現其他操作選項的內容:

scala> (aOption.validOr("no a") |@| aOption.validOr("no a again")) map {_ + "!" + _} 
res18: cats.data.Validated[cats.data.NonEmptyList[String],String] = Valid(a!a) 

scala> (aOption.validOr("no a") |@| bOption.validOr("no b") |@| cOption.validOr("no c")) map {_ + _ + _} 
res27: cats.data.Validated[cats.data.NonEmptyList[String],String] = Invalid(NonEmptyList(no b, no c)) 

這兩個c在這XorValidated是Scala的Either的變化,但XorValidated之間的區別是,Xor(和Either)則多爲「快速失敗的」一元的方式通過(對內涵又名做表示法)對比Validated正在使用應用方法(它允許|@|zip)。 flatMap被視爲順序運算符,|@|/zip被視爲並行運算符(不要與執行模型混淆 - 它與運算符的性質是正交的)。您可以閱讀更多貓的文檔:Validated,Xor

1

函數式編程往往作品很多更乾淨,如果你放棄內置語法和DSL的支持只是做簡單的數據結構最簡單的操作

val options = List((aOption, "a"), 
        (bOption, "b"), 
        (cOption, "c"), 
        (dOption, "d")) 

val undefinedOptions = options filterNot (_._1.isDefined) 

if (undefinedOptions.isEmpty) 
    println("Process...") 
else 
    undefinedOptions map {case (_,name) => s"$name is not defined"} foreach println 

有時候單子簡化你的代碼,有時他們別。不要忘了你可以像Options一樣對待古老的無聊物品。

+0

只是提一提,這種做法將不能提取選項值。你必須再做一步:'val List(a,b,c,d)= options .map(_._ 1.get)',這不是很安全 - 因爲存在匹配異常的風險在運行時!!!)如果你不匹配提取器與'List'的大小。除了'List'對於異構數據來說不是一個好的結構--OP並沒有說所有的選項都是相同的類型(你在這裏假設) - 它可能是'aOption:Option [String]','bOption :Option [Int]'等 – dk14

+0

輕微:嚴格地說當你直接使用日誌記錄時(沒有Writer monad這是不切實際的) - 它不再是函數式編程 - 沒有什麼不好,因爲FP不是記錄和調試的最佳方法,但我仍然不會稱之爲「功能性」,也許只是「scala」。 – dk14

+0

'printlns'並不是最終解決方案的代表,只是一個佔位符示例。不,你沒有運行時異常的風險,因爲你已經檢查過這個條件,'Options'列表是不可變的。同樣爲了這個目的,「List」是否是同質的並不重要。它可以是一個List [(Option [Any],String)]'並且工作得很好。 'process'步驟不必從我的列表中提取它們。 –

0

使用foldLeft

使用foldLeft保持indexresult列表中,這樣索引可以幫助記錄和名單是從選項檢索值後的結果的列表。

注意process給出空列表,如果其中任何一個選項是沒有

val options = List(Some(1), Some(2), Some(3), None, None, Some(4), None) 

def process[A, B](options: List[Option[A]])(f: (Int, Option[A]) => Option[B]): List[B] = { 
    val result = 
    options.foldLeft(List.empty[B] -> 0) { (r, c) => 
     val (result, index) = r 
     f(index, c).map(result ++ List(_) -> (index + 1)).getOrElse(result -> (index + 1)) 
    } 
    if (result._1.length == options.length) result._1 else List.empty[B] 
} 


process[Int, Int](options) { (index, current) => 
    current.orElse { 
    println(s"$index is none.") 
    current 
    } 
}.foreach(println) 
1

當你正在處理這可能會失敗,並想知道爲什麼他們中的一個失敗原因的計算,你可以使用Either monad或來自Scalaz等的Validation。我問過這樣的問題(Using Either to process failures in Scala code),所以我建議你看看它,因爲它有一些很好的答案。我之前問過它,答案是在Scala 2.10發佈之前編寫的,其中標準庫有另一個很好的monad - scala.util.Try[T],它(引用文檔)表示可能導致異常的計算,或者返回成功計算的值

當計算的一個失敗,一個場景:

scala> for { a <- Try(Some(1).getOrElse(sys.error("a is none"))) 
      b <- Try(Option.empty[Int].getOrElse(sys.error("b is none"))) } 
     yield a+b 
res1: scala.util.Try[Int] = Failure(java.lang.RuntimeException: b is none) 

當所有的計算成功的場景:

scala> for { a <- Try(Some(1).get) 
      b <- Try(Some(2).get) } 
     yield a+b 
res2: scala.util.Try[Int] = Success(3) 
+0

謝謝。在我的情況下,如果其中一個值沒有,我不想提出錯誤。我只想記錄該值爲none,並返回Option.None作爲結果。 –

+1

@KnowsNotMuch這不是關於「引發錯誤」,更多的是瞭解更多信息。 ''或''可能包含'T'或其他任何東西(例如用於驗證的'String'),'Try'可能包含'T'或者一個異常。因此,你可能會得到一個計算失敗的正式原因,而不是相當神祕的「變量'a'是'None'」。 –

相關問題