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