Scala Success / Failure Chain Template

I have a workflow like this:

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

Any of these steps may fail. I would like to implement this in Scala, it is preferable that parallel branches are evaluated independently of each other, combining both of their errors. Perhaps in a monadic style, but I'm curious about the general OOP pattern. I currently have several options hardcoded for various actions with a chain like this

 def loadLeftTemplateAndForth (leftPath : String, rightPath : String) = { val (template, errors) = loadTemplate(leftPath) if(errors.isEmpty) loadRightTemplateAndForth(template, rightPath) else popupMessage("Error.") } 

which, I am sure, should be some kind of antipattern. The steps need to be separated from the workflow, but I could not come up with something extremely elegant, and should already be checked.

EDIT: So, I tried unsuccessfully to implement something like this

 (((parseTemplate(path1) :: HNil).apply(checkConsistency _) :: ((parseTemplate(path2) :: HNil).apply(checkConsistency _)) :: HNil).apply(checkConformance _) def checkConformance (t1 : Template)(t2 : Template) : Seq[Error] 

Then the functions return Success (result) or Failure (errors). I used HLists, but got lost in type inference rules and other issues. I think I was pretty close. For someone who knew about this, it would probably be a piece of cake.

EDIT: I finally managed to implement this

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

with some immoderate restrictions, that each function should return my Either equivalent and that the error type of the function used should be a subtype of the type of argument error. I did this using the HList, the application class, and the Successful / UnsuccessfulArgList wrapper class.

+4
source share
3 answers

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 kind of error holder and pass it (for parseTemplate, for negotiation, for compliance) or use ThreadLocal.

Much more is separated here:

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

EDIT

I got 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)) 
+1
source

Restore the loadTemplate Either[List[String], Template] methods.

To return Left(List("error1",...)) errors Left(List("error1",...)) and return Right(template) success.

Then you can do

 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 

The above is not fast, i.e. will not combine errors from two branches. If the first failure, it will return a Left and will not evaluate the second. See this project for code for handling error accumulation with Either .

Alternatively, you can use the Scalaz check. See Verifying Method Parameters in Scala, for understanding and the monad for a good explanation.

0
source

Thus, I managed to do this (he can still use refinements - for example, so that he creates a sequence of errors with a common type of a general list of errors and errors of functions):

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

Using

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

All Articles