如果出错,Scala 转换链将停止
Scala chain of transformations stopping if error
我想对字符串应用一系列转换,但在出现错误时停止。这是一个更通用模式的示例(一种责任链模式或访问者模式)
如果可能的话,我现在想避免使用 Cats 或 Scalaz。如果您知道如何在 plain 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!!!!"))
有什么建议吗?
谢谢!!
在 Scala 2.12 中,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!!!!)
但是,如果您想 return 应用所有转换直到出现错误的结果,即
assert(transform("Whatever", tError) == Right("Whatever_One"))
那么 transform
函数的以下重构可能会起作用:
def transform(txt: String, transformers: Seq[Transformer]): Either[Error, String] = {
type Current = Either[Error, String]
type Previous = Either[Error, String]
def foldLeftWithEarlyReturn: Tuple2[Current, Previous] = {
transformers.foldLeft[Tuple2[Current, Previous]](Right(txt) , Right(txt)){
(result, t) => result match {
case ( Right(txt) , Right(previousTxt) ) => ( t.transform(txt) , Right(txt) )
case ( Left(error) , Right(previousTxt) ) => return ( Right(previousTxt), Left(error) )
case e => e
}
}
}
if (foldLeftWithEarlyReturn._1.isLeft)
foldLeftWithEarlyReturn._2 // this means last transformation in sequence resulted in Left, so return previous
else
foldLeftWithEarlyReturn._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)
}
纯 Scala
可能,使代码短路的最简单方法是使用 return
语句。它 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
语句将立即 return 从 transform
函数遇到的第一个 Left
。
猫
在猫身上,你真的不需要做任何特别的事情。它会自动短路某些 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])
}
我想对字符串应用一系列转换,但在出现错误时停止。这是一个更通用模式的示例(一种责任链模式或访问者模式)
如果可能的话,我现在想避免使用 Cats 或 Scalaz。如果您知道如何在 plain 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!!!!"))
有什么建议吗?
谢谢!!
在 Scala 2.12 中,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!!!!)
但是,如果您想 return 应用所有转换直到出现错误的结果,即
assert(transform("Whatever", tError) == Right("Whatever_One"))
那么 transform
函数的以下重构可能会起作用:
def transform(txt: String, transformers: Seq[Transformer]): Either[Error, String] = {
type Current = Either[Error, String]
type Previous = Either[Error, String]
def foldLeftWithEarlyReturn: Tuple2[Current, Previous] = {
transformers.foldLeft[Tuple2[Current, Previous]](Right(txt) , Right(txt)){
(result, t) => result match {
case ( Right(txt) , Right(previousTxt) ) => ( t.transform(txt) , Right(txt) )
case ( Left(error) , Right(previousTxt) ) => return ( Right(previousTxt), Left(error) )
case e => e
}
}
}
if (foldLeftWithEarlyReturn._1.isLeft)
foldLeftWithEarlyReturn._2 // this means last transformation in sequence resulted in Left, so return previous
else
foldLeftWithEarlyReturn._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)
}
纯 Scala
可能,使代码短路的最简单方法是使用 return
语句。它 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
语句将立即 return 从 transform
函数遇到的第一个 Left
。
猫
在猫身上,你真的不需要做任何特别的事情。它会自动短路某些 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])
}