This is a follow-up to my previous question
Suppose I need to validate an XML like this:
<a><a1>xxx<a1/><a2>yyy</a2><a3>zzz</a3></a>
I need to make sure that the root element has label a
and also has children <a1>xxx</a1>
, <a2>yyy</a2>
, <a3>zzz</a3>
in this order.
I'd like to use List[String]
to collect errors and define a function to validate a single XML element like this:
type ValidateSingleElement = Elem => List[String]
Now I can write functions to validate the label, text, and attributes of a given XML element:
val label : String => ValidateSingleElement = ...
val text : String => ValidateSingleElement = ...
val attr : (String, String) => ValidateSingleElement = ...
I can compose these functions with |+|
since ValidateSingleElement
is a monoid.
val a1 = label("a1") |+| text("xxx") // validate both label and text
Now I need a function to validate the children of a given element. In order to write such a function I need another function to validate a sequence of elements
val children: ValidateElements => ValidateSingleElement = ...
The ValidateElements
is defined as follows:
type ValidateElements = List[Elem] => Writer[List[String], List[Elem]]
I am using the List[String]
and Writer
monad to collect errors while traversing the sequence of elements.
Now I can write a function to validate the children of a given element:
val children: ValidateElements => ValidateSingleElement =
validateElements => {e =>
val kids = e.child collect {case e:Elem => e}
val writer = validateElements(kids.toList)
writer.written
}
... and validate the first element of the sequence:
val child: ValidateSingleElement => ValidateElements = validate => {
_ match {
case e:es => Writer(validate(e), es)
case _ => Writer(List("Unexpected end of input"), Nil)
}
}
Finally I can re-define ValidateElements
as Kleisli
type ErrorsWriter[A] = Writer[List[String], A]
type ValidateElements = Kliesli[ErrorsWriter, List[Elem], List[Elem]]
... and re-write the child
to return the Kleisli
instead of the function.
Given both the child
and children
I can write a
-- a validating function for the XML from above:
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))
Does it make sense ? How would you correct/extend this design ?
Well, in most cases you don't want to only validate an XML document, you want to create some meaningful business object from it, and your code doesn't seem to allow for that. I think Play's type-class based Json library is a good model for how to do this. It allows you to define
Reads
objects, where aReads[A]
is essentially aJsValue => Either[Errors, A]
. These can be flexibly combined with a bunch of combinators shipped with the library.