2013-03-31 2 views
4

У меня есть рабочий процесс, как это:Успех/неудача картины цепи в Scala

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

Либо один из этих шагов может потерпеть неудачу. Я хотел бы реализовать это в Scala, желательно, чтобы параллельные ветви оценивались независимо друг от друга, объединяя обе свои ошибки. Возможно, в монадическом стиле, но мне любопытно и об общем шаблоне ООП. В настоящее время у меня есть несколько вариантов жёстко прописанных для различных действий с цепочкой, как этого

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

который я ставка должна быть каким-то антипаттерны. Шагам нужно отделить от рабочего процесса, но я не смог придумать что-нибудь чрезвычайно элегантное, и уже должны быть проверены пути.

EDIT: Ok, так что я безуспешно пытался реализовать что-то вроде этого

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

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

функции затем вернуть успех (результат) или Failure (ошибки). Я использовал HLists, но потерялся в правилах вывода типов и других проблемах. Кажется, я был довольно близок. Для кого-то, кто знал об этом, вероятно, это был бы кусок пирога.

EDIT: я, наконец, удалось осуществить эту

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

с некоторыми unfornate ограничениями, что каждая функция должна возвращать свой эквивалент либо и что тип ошибки прикладной функции должен быть подтипом аргументов 'тип ошибки. Я сделал это, используя HList, класс приложений и класс-оболочку Successful/UnsuccessfulArgList.

ответ

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

EDIT

я закончил с чем-то вроде этого:

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)) 
+0

Спасибо. Однако это кажется довольно жестким. синтаксический разбор может также не сгенерировать шаблон вообще. Ваше решение кажется связанным только с одним типом и его преобразованиями. Смотрите мое редактирование выше для того, на что я надеялся. –

+0

Теперь выглядит намного лучше, спасибо вам тоже за вопрос – idonnie

0

Пусть ваши 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 и не будет оценивать второй.См. this project для кода для обработки накоплений ошибок с использованием Either.

В качестве альтернативы вы можете использовать проверку Scalaz. См. Method parameters validation in Scala, with for comprehension and monads за хорошее объяснение.

+0

Спасибо за ссылку Scalaz, я должен проверить ее когда-нибудь. –

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