当我有多个选项并且我必须仅在所有选项都有值时处理某些内容时,for comprehension
提供了编写代码的好方法
for {
a <- aOption
b <- bOption
c <- cOption
d <- dOption
} yield {...process...}
虽然这是非常有用且优雅而简洁的编写代码的方式,但我会错过记录的能力,如果说&#34; cOption&#34;没有得到任何值,因此处理没有发生。
上面的代码中是否有一个很好的方法,能够记录缺失的值而不需要使用嵌套的ifs。
答案 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
或来自cats的Validated
,那么您关于脓肿的信息将被表示为无效状态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))
猫的Xor
和Validated
都是scala Either
的变体,但Xor
和Validated
之间的区别是Xor
(和Either
)更多采用&#34;快速失败&#34;与Validated
使用应用方法(允许|@|
和zip
)相比,monadic方法(用于理解又称做法)。 flatMap
被视为顺序运算符,|@|
/ zip
被视为并行运算符(不要与执行模型混淆 - 它与运算符的性质正交) 。您可以在猫文档中阅读更多内容:Validated,Xor。
答案 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
维护index
和result
列表,以便索引可以帮助记录和列表是从选项中检索值后的结果列表。
请注意,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)