如果错误,Scala链转换停止

时间:2018-05-01 23:18:45

标签: scala cat scalaz

我想将一系列转换应用于String,但在出现错误时停止。这是一个更一般的模式(一种责任链模式或访客模式)的例子

如果有可能,我想暂时避免使用Cats或Scalaz。如果您知道如何在普通Scala以及Cats / Scalaz 上做到这一点,我很乐意在答案中看到代码;)

以下是我的方法(代码末尾的断言),但发现错误时没有停止。基本上是跳过X次转换的执行。

type Error = String

sealed trait Transformer {
  def transform(txt:String) : Either[Error, String]
}

object Transformer1 extends Transformer {
  override def transform(txt: String): Either[Error, String] = Right(s"${txt}_One")
}

object Transformer2 extends Transformer {
  override def transform(txt: String): Either[Error, String] = Right(s"${txt}_Two")
}

object Transformer3 extends Transformer {
  override def transform(txt: String): Either[Error, String] = Right(s"${txt}_Three")
}

object TransformerError extends Transformer {
  override def transform(txt: String): Either[Error, String] = Left("Error!!!!")
}

def transform(txt: String, transformers: Seq[Transformer]): Either[Error, String] =
  transformers.foldLeft(Right(txt):Either[Error, String])( (result, t) =>  result match {
    case Right(txt) => t.transform(txt)
    case error => error
  } )


val tOk = Seq(Transformer1, Transformer2, Transformer3)
val tError = Seq(Transformer1, TransformerError, Transformer3)

assert(transform("Whatever", tOk) == Right("Whatever_One_Two_Three"))
assert(transform("Whatever", tError) == Left("Error!!!!"))

有什么建议吗?

谢谢!

3 个答案:

答案 0 :(得分:2)

在Scala 2.12中,<xsl:copy-of select="!@BadAttyName"/> 是偏右的,因此Either可以解决问题。

for-yield

评估为for { v1 <- Transformer1.transform("Whatever") v2 <- Transformer2.transform(v1) v3 <- Transformer3.transform(v2) } yield { v3 } ,而

Right(Whatever_One_Two_Three)

评估为for { v1 <- Transformer1.transform("Whatever") v2 <- TransformerError.transform(v1) v3 <- Transformer3.transform(v2) } yield { v3 }

但是,如果您希望返回应用了所有转换的结果,直到达到错误,即

Left(Error!!!!)

然后 assert(transform("Whatever", tError) == Right("Whatever_One")) 函数的以下重构可能会起作用:

transform

答案 1 :(得分:1)

处理集合时,如果要提前终止,则通常需要转向递归。

def transform(txt :String
             ,transformers :Seq[Transformer]
             ): Either[Error, String] = transformers match {
  case Seq() => Right(txt)
  case hd +: tl => hd.transform(txt).fold(Left(_), transform(_, tl))
}

如果不那么简洁,也可以使用尾递归版本。

@tailrec
def transform(txt :String
             ,transformers :Seq[Transformer]
             ): Either[Error, String] = transformers match {
  case Seq() => Right(txt)
  case hd +: tl =>
    val rslt = hd.transform(txt)
    if (rslt.isLeft) rslt else transform(rslt.toSeq.head, tl)
}

答案 2 :(得分:0)

Pure Scala

使代码短路的最简单方法可能是使用return语句。它返回包含在

中的最里面的命名函数的结果
def transform(txt: String, transformers: Seq[Transformer]): Either[Error, String] =
  transformers.foldLeft(Right(txt):Either[Error, String])( (result, t) =>
    result match {
      case Right(txt) => t.transform(txt)
      case error => return error
  } )

因此,此代码中的return error语句将立即返回Left函数遇到的第一个transform

在猫咪中,你真的不需要做任何特别的事情。它会自动将某些monad的某些调用短路,因为monad必须实现tailRecM,而一些monad(包括Either)以懒惰的方式实现它,以避免做无用的flatMap

import cats.implicits._

def transformCats(txt: String, transformers: List[Transformer]): Either[Error, String] = {
  // It seems this is needed to help Scala with type inference.
  type Result[T] = Either[Error, T] 
  // foldLeftM is implemented in terms of tailRecM, 
  // and thus is short-circuiting for Either
  transformers.foldLeftM(txt)((result, tf) => tf.transform(result): Result[String])
}