Проверка XML с помощью Writer и Kleisli в Scala
Это продолжение моего предыдущего вопроса
Предположим, мне нужно проверить 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
Монаду собирать ошибки при обходе последовательности элементов.
Теперь я могу написать функцию для проверки дочерних элементов данного элемента:
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))
Имеет ли это смысл? Как бы вы исправили / расширили этот дизайн?
1 ответ
Ну, в большинстве случаев вы не хотите проверять только XML-документ, вы хотите создать из него какой-то значимый бизнес-объект, и ваш код, похоже, этого не допускает. Я думаю, что библиотека Json, основанная на классах типов, - хорошая модель того, как это сделать. Это позволяет вам определить Reads
объекты, где Reads[A]
по сути JsValue => Either[Errors, A]
, Их можно гибко комбинировать с кучей комбинаторов, поставляемых с библиотекой.