5

我有这样的工作流程:

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

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

4

3 回答 3

1

How about this?

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

After that:

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

Create some holder for errors, and pass it around (to parseTemplate, to consistent, to conforms), or use ThreadLocal.

Here is decoupled much more:

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

EDIT

I've ended up with something like this:

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))
于 2013-03-31T23:41:23.387 回答
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

以上是“快速失败”,即不会合并来自两个分支的错误。如果第一个失败,它将返回 aLeft并且不会评估第二个。有关使用Either. _

或者,您可以使用 Scalaz 验证。请参阅Scala 中的方法参数验证,以及理解和 monads以获得很好的解释。

于 2013-04-01T20:32:05.533 回答
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()))
于 2013-04-02T15:51:00.493 回答