处理多个选项并记录未找到的场景

时间:2016-10-16 02:32:49

标签: scala

当我有多个选项并且我必须仅在所有选项都有值时处理某些内容时,for comprehension提供了编写代码的好方法

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

虽然这是非常有用且优雅而简洁的编写代码的方式,但我会错过记录的能力,如果说&#34; cOption&#34;没有得到任何值,因此处理没有发生。

上面的代码中是否有一个很好的方法,能够记录缺失的值而不需要使用嵌套的ifs。

4 个答案:

答案 0 :(得分: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

如果您需要举报更多内容 - 最好的方法是使用来自scalaz的Validation或来自catsValidated,那么您关于脓肿的信息将被表示为无效状态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)

我使用|+|运算符假设连接,但您也可以使用applicative builder(或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))

猫的XorValidated都是scala Either的变体,但XorValidated之间的区别是Xor(和Either)更多采用&#34;快速失败&#34;与Validated使用应用方法(允许|@|zip)相比,monadic方法(用于理解又称做法)。 flatMap被视为顺序运算符,|@| / zip被视为并行运算符(不要与执行模型混淆 - 它与运算符的性质正交) 。您可以在猫文档中阅读更多内容:ValidatedXor

答案 1 :(得分: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

有时monad会简化您的代码,有时则不会。不要忘记你也可以将Options视为普通的无聊对象。

答案 2 :(得分:1)

当您处理可能失败的计算并希望得出其中一个失败的原因时,您可以使用Scalaz中的Either monad或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)

答案 3 :(得分: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)