2016-10-16 31 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'”。 –

相关问题