1

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 ?

Community
  • 1
  • 1
Michael
  • 41,026
  • 70
  • 193
  • 341
  • Why do you want to use `Writer[List[String], A]` instead of `ValidationNel[String, A]` ? – Peter Neyens Jan 30 '16 at 21:06
  • I'd like to compose `child` functions with `>=>`. How can I compose functions of type `List[Elem] => ValidationNel[String, List[Elem]]` ? – Michael Jan 31 '16 at 05:09
  • You'd need to import `Validation.FlatMap._` and provide you own `Bind` instance, for example like in https://gist.github.com/channingwalton/3230464 – Kolmar Jan 31 '16 at 16:23
  • @Kolmar Thanks, interesting. What are advantages of this solution in comparison with mine ? – Michael Jan 31 '16 at 19:18
  • I'm not sure. I believe `|+|` and `>=>` stay the same. `ValidationNel[YourErrorType, ResultType]` seems to be more semantically correct, than `Writer[List[YourErrorType], ResultType]` – you get *either* a non-empty list of errors, *or* the result. It's a little bit more convenient to work with validations than with writers (`foo.failureNel`, etc.). But you have to work through this `Bind[Validation ...]` instance which many people find a bad practice. – Kolmar Jan 31 '16 at 20:11
  • Thanks for the explanation. Why would many people consider `Bind[Validation ...]` bad practice ? – Michael Feb 01 '16 at 19:45

1 Answers1

1

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 a Reads[A] is essentially a JsValue => Either[Errors, A]. These can be flexibly combined with a bunch of combinators shipped with the library.

Matthias Berndt
  • 4,387
  • 1
  • 11
  • 25
  • It can, you just need to provide a suitable Applicative instance. Anyway, it's irrelevant because a Reads[A] is not a function type and its reads method doesn't return Either but JsResult (and Errors isn't even a type). Now you can complain about those details, or you can think about why it says "essentially". – Matthias Berndt Jan 31 '16 at 10:42
  • I am bit confused, sorry. How would you define a function to validate a sequence of XML elements and collect the validation errors ? Please note that I don't use `play` and would not like to add it as a dependency. – Michael Jan 31 '16 at 11:03