【问题标题】:Success/failure chain pattern in ScalaScala 中的成功/失败链模式
【发布时间】:2013-03-31 14:05:40
【问题描述】:

我有这样的工作流程:

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

这些步骤中的任何一个都可能失败。我想在Scala中实现它,最好是让并行分支得到独立评估,合并它们的两个错误。也许是单子风格,但我也对一些通用的 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(result) 或 Failure(errors)。我正在使用 HLists,但在类型推断规则和其他问题中迷失了方向。看来我已经很接近了。对于熟悉这些东西的人来说,这可能是小菜一碟。

编辑: 我终于设法实现了这个

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

有一些不合理的约束,每个函数必须返回我等效的 Either 并且应用函数的错误类型必须是参数错误类型的子类型。我使用 HList、应用程序类型类和包装器类成功/不成功ArgList。

【问题讨论】:

    标签: scala design-patterns workflow


    【解决方案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))
    

    【讨论】:

    • 谢谢。然而,这似乎相当僵化。解析也可能失败,根本不产生模板。您的解决方案似乎仅限于一种类型及其转换。请参阅上面的编辑以了解我的期望。
    【解决方案2】:

    让您的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
    

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

    您也可以使用 Scalaz 验证。请参阅Method parameters validation in Scala, with for comprehension and monads 以获得很好的解释。

    【讨论】:

    • 感谢 Scalaz 链接,总有一天我必须检查一下。
    【解决方案3】:

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

    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()))
    

    【讨论】:

      猜你喜欢
      • 2012-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多