有没有一种优雅的方式来处理 Scala 中的任一 Monad?



我正在从Scala开始,用circe做一个处理JSON的项目。

我遇到了很多来自函数的 Either 返回,我似乎没有找到一种优雅的方式来处理所有这些返回。

例如,对于单个,我按照以下代码片段进行操作:

if (responseJson.isRight) {
//do something
} else {
//do something else
}

但是当我有很多顺序时,我该怎么办,例如这个例子,我只是直接转到右侧,我觉得我应该做一些额外的验证:

ClassA(
someValue,
someValue,
someJson.hcursor.get[Double]("jsonKey1").right.get,
someJson.hcursor.get[Double]("jsonKey2").right.get,
someJson.hcursor.get[Double]("jsonKey3").right.get
)

如果它们是Right,当我想获取它们的内容时,我应该/如何处理多个Either对象(而不会以一堆 if-else 或类似对象结束(,但不是我不确定它们总是一个Right

假设你有一个案例类,

case class Demo(i: Int, s: String)

和两个要么,

val intEither: Either[Throwable, Int] = ???
val stringEither: Either[Throwable, Int] = ???

所以......让我们从最基本和最明显的一个开始,

val demoEither: Either[Throwable, Demo] = 
intEither.flatMap(i => 
stringEither.map(s => Demo(i, s))
)

另一种方法是与上面一样

,即使用理解,
val demoEither: Either[Throwable, Demo] = 
for {
i <- intEither 
s <- stringEither
} yield Demo(i, s)

但是,monads是顺序的,这意味着如果第一个EitherLeft那么你甚至不会看第二个Either,而只会得到一个Left。这对于验证来说通常是不可取的,因为您不想丢失所有组件的验证信息,因此您真正想要的是Applicative

Either不是Applicative,您将不得不为此使用catsscalaz或实现您自己的应用程序。

CAT 为此明确用途提供了Validated应用,允许您验证并保留已验证组件的所有错误信息。

import cats.data._
import cats.implicits._
val intValidated: ValidatedNec[Throwable, Int] = 
intEither.toValidatedNec
val stringValidated: ValidatedNec[Throwable, String] =
stringEither.toValidatedNec
val demoValidated: ValidatedNec[Throwable, Demo] = 
(intValidated, stringValidated).mapN(Demo)
val demoEither: Either[List[Throwable], Demo] = 
demoValidated.leftMap(errorNec => errorNec.toList)

或者,如果您只执行此操作一次并且不想依赖cats,则可以仅使用非常通用的模式匹配

val demoEither: Either[List[Throwable], Demo] = 
(intEither, stringEither) match {
case (Right(i), Right(s)) => Right(Demo(i, s))
case (Left(ti), Left(ts)) => Left(List(ti, ts))
case (Left(ti), _) => Left(List(ti))
case (_, Left(ts)) => Left(List(ts))
}

我想获取它们的内容时,我应该/如何处理多个Either对象(而不会以一堆if-else或类似对象结束(,如果它们是Right,但不是我不确定它们总是Right

因此,您有一些Either实例,它们都具有相同类型的签名。

val ea :Either[Throwable,String] = Right("good")
val eb :Either[Throwable,String] = Left(new Error("bad"))
val ec :Either[Throwable,String] = Right("enough")

并且您希望所有Right值,忽略任何Left值。

List(ea, eb, ec).collect{case Right(x) => x}
//res0: List[String] = List(good, enough)

你不知道哪个Either包含哪个String但我认为这就是你所要求的。

最新更新