在Scala中使用Writer和Kleisli验证XML

时间:2016-01-30 19:23:46

标签: scala scalaz kleisli writer-monad

这是我之前question

的后续内容

假设我需要像这样验证XML:

<a><a1>xxx<a1/><a2>yyy</a2><a3>zzz</a3></a>

我需要确保根元素具有标签a,并且还按此顺序生成子<a1>xxx</a1><a2>yyy</a2><a3>zzz</a3>

我想使用List[String]收集错误并定义一个函数来验证单个XML元素,如下所示:

type ValidateSingleElement = Elem => List[String]

现在我可以编写函数来验证给定XML元素的标签,文本和属性:

val label : String => ValidateSingleElement = ...
val text  : String => ValidateSingleElement = ...
val attr  : (String, String) => ValidateSingleElement = ...

我可以使用|+|撰写这些函数,因为ValidateSingleElement是一个幺半群。

val a1 = label("a1") |+| text("xxx") // validate both label and text

现在我需要一个函数来验证给定元素的子元素。为了编写这样的函数,我需要另一个函数来验证元素的序列

val children: ValidateElements => ValidateSingleElement = ...

ValidateElements的定义如下:

type ValidateElements = List[Elem] => Writer[List[String], List[Elem]]

我正在使用List[String]Writer monad在遍历元素序列时收集错误。

现在我可以编写一个函数来验证给定元素的子元素:

val children: ValidateElements => ValidateSingleElement = 
  validateElements => {e =>
    val kids = e.child collect {case e:Elem => e}
    val writer = validateElements(kids.toList)
    writer.written
  }

...并验证序列的第一个元素:

 val child: ValidateSingleElement => ValidateElements = validate => {
   _ match {
     case e:es => Writer(validate(e), es)
     case _    => Writer(List("Unexpected end of input"), Nil)
   }
 }

最后,我可以将ValidateElements重新定义为Kleisli

type ErrorsWriter[A]  = Writer[List[String], A]
type ValidateElements = Kliesli[ErrorsWriter, List[Elem], List[Elem]]

...并重新编写child以返回Kleisli而不是函数。

鉴于childchildren,我可以编写a - 上面的XML验证函数:

val a1 = label("a1") |+| text("xxx")
val a2 = label("a2") |+| text("yyy")
val a3 = label("a3") |+| text("zzz")
val a  = label("a")  |+| children(child(a1) >=> child(a2) >=> child(a3))

有意义吗?您如何纠正/扩展此设计?

1 个答案:

答案 0 :(得分:1)

好吧,在大多数情况下,您不希望仅验证XML文档,您希望从中创建一些有意义的业务对象,而您的代码似乎不允许这样做。我认为Play的基于类型的Json库是如何做到这一点的好模型。它允许您定义Reads个对象,其中Reads[A]基本上是JsValue => Either[Errors, A]。这些可以与库附带的一堆组合器灵活组合。