这是我之前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
而不是函数。
鉴于child
和children
,我可以编写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))
有意义吗?您如何纠正/扩展此设计?
答案 0 :(得分:1)
好吧,在大多数情况下,您不希望仅验证XML文档,您希望从中创建一些有意义的业务对象,而您的代码似乎不允许这样做。我认为Play的基于类型的Json库是如何做到这一点的好模型。它允许您定义Reads
个对象,其中Reads[A]
基本上是JsValue => Either[Errors, A]
。这些可以与库附带的一堆组合器灵活组合。