Scala中的成功/失败链模式

时间:2013-03-31 14:05:40

标签: scala design-patterns workflow

我有这样的工作流程:

parse template -> check consistency
                                    -> check conformance of one template to another
parse template -> check consistency

其中一个步骤可能会失败。我想在Scala中实现它,最好是让并行分支独立评估,合并它们的错误。也许是一种monadic风格,但我也对一些一般的OOP模式感到好奇。目前我有多种变体硬编码用于像这样的链接的各种动作

def loadLeftTemplateAndForth (leftPath : String, rightPath : String) = {
  val (template, errors) = loadTemplate(leftPath)
  if(errors.isEmpty) loadRightTemplateAndForth(template, rightPath)
  else popupMessage("Error.")
}
我打赌它必须是某种反模式。这些步骤需要与工作流程脱钩,但我无法想出任何非常优雅的东西,并且必须已经证明了这些方法。

修改 好的,所以我没有成功地尝试实现这样的东西

(((parseTemplate(path1) :: HNil).apply(checkConsistency _) :: ((parseTemplate(path2) :: HNil).apply(checkConsistency _)) :: HNil).apply(checkConformance _)

def checkConformance (t1 : Template)(t2 : Template) : Seq[Error]

然后函数将返回Success(结果)或Failure(错误)。我使用的是HLists,但在类型推理规则和其他问题上迷失了方向。虽然看起来我很近。对于知道这些东西的人来说,这可能是件小事。

修改 我终于设法实现了这个

(parseTemplate("Suc") :: Args).apply(checkConsistency _) :: 
(parseTemplate("Suc") :: Args).apply(checkConsistency _) :: Args)
.apply(checkConformance _)

有一些非强制约束,每个函数必须返回我的Either的等价物,并且应用函数的错误类型必须是参数'错误类型的子类型。我使用HList,应用程序类型类和包装类Successful / UnsuccessfulArgList完成了它。

3 个答案:

答案 0 :(得分:1)

这个怎么样?

// Allows conditional invocation of a method
class When[F](fun: F) {
    def when(cond: F => Boolean)(tail: F => F) = 
      if (cond(fun)) tail(fun) else fun
}
implicit def whenever[F](fun: F): When[F] = new When[F](fun)

之后:

parseTemplate(t1).when(consistent _){ 
  val parsed1 = _
  parseTemplate(t2).when(consistent _){ 
    conforms(parsed1, _) 
  }
}

为错误创建一些持有者,并传递它(到parseTemplate,一致,符合),或使用ThreadLocal。

这里解耦得更多:

(parseTemplate(t1), parseTemplate(t2))
  .when(t => consistent(t._1) && consistent(t._2)){ t =>
    conforms(t._1, t._2) 
  }

修改

我最终得到了类似的东西:

def parse(path: String): Either[
  String,  // error
  AnyRef   // result
] = ?

def consistent(result: Either[String, AnyRef]): Either[
  String,  // error
  AnyRef   // result
] = ?

def conforms(result1: Either[String, AnyRef], result2: Either[String, AnyRef], 
  fullReport: List[Either[
    List[String],  // either list of errors 
    AnyRef         // or result
  ]]): List[Either[List[String], AnyRef]] = ?

( (parse("t1") :: Nil).map(consistent _), 
  (parse("t2") :: Nil).map(consistent _)
).zipped.foldLeft(List[Either[List[String], AnyRef]]())((fullReport, t1t2) =>
  conforms(t1t2._1, t1t2._2, fullReport))

答案 1 :(得分:1)

loadTemplate方法返回Either[List[String], Template]

错误返回Left(List("error1",...)),成功返回Right(template)

然后你可以做

type ELT = Either[List[String], Template]

def loadTemplate(path: String): ELT = ...

def loadRightTemplateAndForth(template: Template, rightPath: String): ELT = ...

def loadLeftTemplateAndForth(leftPath: String, rightPath: String): ELT =
  for {
    lt <- loadTemplate(leftPath).right
    rt <- loadRightTemplateAndForth(lt, rightPath).right
  } yield rt

以上是“快速失败”,也就是说,它不会合并两个分支的错误。如果第一个失败,它将返回Left并且不会评估第二个。有关使用Either处理错误累积的代码,请参阅this project

或者,您可以使用Scalaz验证。有关详细说明,请参阅Method parameters validation in Scala, with for comprehension and monads

答案 2 :(得分:0)

所以我设法做到这一点的方式是这样的(它仍然可以使用一个改进 - 例如,它使用列表错误和函数错误共同的类型构造错误序列):

<强> HList.scala

import HList.::

sealed trait HList [T <: HList[T]] {

  def ::[H1](h : H1) : HCons[H1, T]

}

object HList { 

  type ::[H, T <: HList[T]] = HCons[H, T] 

  val HNil = new HNil{}

}

final case class HCons[H, T <: HList[T]](head: H, tail: T) extends HList[HCons[H, T]] {

  override def ::[H1](h: H1) = HCons(h, this)

  def apply[F, Out](fun : F)(implicit app : HApply[HCons[H, T], F, Out]) = app.apply(this, fun)

  override def toString = head + " :: " + tail.toString

  None
}

trait HNil extends HList[HNil] {
  override def ::[H1](h: H1) = HCons(h, this)
  override def toString = "HNil"
}

<强> HListApplication.scala

@implicitNotFound("Could not find application for list ${L} with function ${F} and output ${Out}.")
trait HApply[L <: HList[L], -F, +Out] {
  def apply(l: L, f: F): Out
}

object HApply {

  import HList.::

  implicit def happlyLast[H, Out] = new HApply[H :: HNil, H => Out, Out] {
    def apply(l: H :: HNil, f: H => Out) = f(l.head)
  }

  implicit def happlyStep[H, T <: HList[T], FT, Out](implicit fct: HApply[T, FT, Out]) = new HApply[H :: T, H => FT, Out] {
    def apply(l: H :: T, f: H => FT) = fct(l.tail, f(l.head))
  }

}

<强> ErrorProne.scala

sealed trait ErrorProne[+F, +S]

case class Success [+F, +S] (result : S) extends ErrorProne[F, S]

case class Failure [+F, +S] (errors : Seq[F]) extends ErrorProne[F, S]

<强> ArgList.scala

import HList.::
import HList.HNil

sealed trait ArgList [E, L <: HList[L]] {

  def apply[F, S](fun : F)(implicit app : HApply[L, F, ErrorProne[E, S]]) 
  : ErrorProne[E, S]

  def :: [A, E1 <: EX, EX >: E] (argument : ErrorProne[E1, A]) : ArgList[EX, A :: L]

}

case class SuccessArgList [E, L <: HList[L]] (list : L) extends ArgList[E, L] {

  def apply[F, S](fun : F)(implicit app : HApply[L, F, ErrorProne[E, S]]) 
  : ErrorProne[E, S] = app.apply(list, fun)

  override def :: [A, E1 <: EX, EX >: E] (argument : ErrorProne[E1, A]) : ArgList[EX, A :: L] = argument match {
    case Success(a) => SuccessArgList(a :: list)
    case Failure(e) => FailureArgList(e)
  }

}

case class FailureArgList [E, L <: HList[L]] (errors : Seq[E]) extends ArgList[E, L] {

  def apply[F, S](fun : F)(implicit app : HApply[L, F, ErrorProne[E, S]]) 
  : ErrorProne[E, S] = Failure(errors)

  override def :: [A, E1 <: EX, EX >: E] (argument : ErrorProne[E1, A]) : ArgList[EX, A :: L] = argument match {
    case Success(a) => FailureArgList(errors)
    case Failure(newErrors) => FailureArgList(Seq[EX]() ++ errors ++ newErrors)
  }

}

object Args {

  def :: [E1, A] (argument : ErrorProne[E1, A]) : ArgList[E1, A :: HNil] = argument match {
    case Success(a) => SuccessArgList(a :: HNil)
    case Failure(e) => FailureArgList(e)
  }

}

<强>用法

val result = ((parseTemplate("Suc") :: Args).apply(checkConsistency _) :: 
              (parseTemplate("Suc") :: Args).apply(checkConsistency _) :: Args)
              .apply(checkConformance _)

trait Err
case class Err1 extends Err
case class Err2 extends Err
case class Err3 extends Err

def parseTemplate(name : String) : ErrorProne[Err, Int] = if(name == "Suc") Success(11) else Failure(Seq(Err1()))

def checkConsistency(value : Int) : ErrorProne[Err2, Double] = if(value > 10) Success(0.3) else Failure(Seq(Err2(), Err2()))

def checkConformance(left : Double) (right : Double) : ErrorProne[Err3, Boolean] = 
    if(left == right) Success(true) else Failure(Seq(Err3()))