
getByPath function, which retrieves an element from the XML tree by its full path. import scala.xml.{Node => XmlNode} def getByPath(path: List[String], root: XmlNode): Option[XmlNode] = path match { case name::names => for { node1 <- root.child.find(_.label == name) node2 <- getByPath(names, node1) } yield node2 case _ => Some(root) } getByPath function refactored to meet the new requirements.createFunctionToExtractChildNodeByName , but let's call it just a child for short. val child: String => XmlNode => Option[XmlNode] = name => node => node.child.find(_.label == name) getByPath function is a sequential composition of functions that retrieve children. The following compose function implements this composition of two functions: getChildA and getChildB . type ExtractXmlNode = XmlNode => Option[XmlNode] def compose(getChildA: ExtractXmlNode, getChildB: ExtractXmlNode): ExtractXmlNode = node => for {a <- getChildA(node); ab <- getChildB(a)} yield ab A => M[A] , where M is a monad . The library defines Kleisli[M, A, B] , a wrapper for A => M[B] , which has a method> => to implement a sequential composition of these Kleisli , like the composition of ordinary functions using andThen . This composition we will call composition Kleisli . The code below shows an example of such a composition: val getChildA: ExtractXmlNode = child(“a”) val getChildB: ExtractXmlNode = child(“b”) import scalaz._, Scalaz._ val getChildAB: Kleisli[Option, XmlNode, XmlNode] = Kleisli(getChildA) >=> Kleisli(getChildB) getByPath function as a composition of child functions that retrieve children. import scalaz._, Scalaz._ def getByPath(path: List[String], root: XmlNode): Option[XmlNode] = path.map(name => Kleisli(child(name))) .fold(Kleisli.ask[Option, XmlNode]) {_ >=> _} .run(root) Kleisli.ask[Option, XmlNode] as a neutral element of the fold method. We need this neutral element to handle the special case where path is empty. Kleisli.ask[Option, XmlNode] is just another designation of a function from any node in Some(node) .getByPathGeneric : def getByPathGeneric[A](child: String => A => Option[A]) (path: List[String], root: A): Option[A] = path.map(name => Kleisli(child(name))) .fold(Kleisli.ask[Option, A]) {_ >=> _} .run(root) getByPathGeneric to retrieve an item from JSON (we use json4s here): import org.json4s._ def getByPath(path: List[String], root: JValue): Option[JValue] = { val child: String => JValue => Option[JValue] = name => json => json match { case JObject(obj) => obj collectFirst {case (k, v) if k == name => v} case _ => None } getByPathGeneric(child)(path, root) } child: JValue => Option[JValue] , to work with JSON instead of XML, but the getByPathGeneric function remained unchanged and works with both XML and JSON.getByPathGeneric even more and abstract it from Option using the Scalaz libraries, which provides an instance of the monad for the Option -- scalaz.Monad[Option] . So we can rewrite getByPathGeneric as follows: import scalaz._, Scalaz._ def getByPathGeneric[M[_]: Monad, A](child: String => A => M[A]) (path: List[String], root: A): M[A]= path.map(name => Kleisli(child(name))) .fold(Kleisli.ask[M, A]) {_ >=> _} .run(root) getByPath function using the getByPathGeneric function: def getByPath(path: List[String], root: XmlNode): Option[XmlNode] = { val child: String => XmlNode => Option[XmlNode] = name => node => node.child.find(_.label == name) getByPathGeneric(child)(path, root) } getByPathGeneric to return an error message if the item is not found. For this we use scalaz. \ / (The so-called “disjunction”) which is the right-hand version of scala.Either .Scalaz provides the “implicit” (implicit) OptionOps class with the toRightDisjunction[B](b: B) method, which converts Option[A] to scalaz.B\/A , so that Some(a) becomes Right(a) and None becomes Left(b) .getByPathGeneric to return an error message instead of None if the item we are looking for is not found. type Result[A] = String\/A def getResultByPath(path: List[String], root: XmlNode): Result[XmlNode] = { val child: String => XmlNode => Result[XmlNode] = name => node => node.child.find(_.label == name).toRightDisjunction(s"$name not found") getByPathGeneric(child)(path, root) } getByPath function processed only data in XML format and returned None if the item was not found. We needed it to also work with the JSON format and return an error message instead of None.Scalaz library allows you to write a generalized getByPathGeneric function, using parameterized types (generics) to support both XML and JSON, as well as scalaz. \ / (Disjunction) to abstract from Option and issue messages about mistakes.Source: https://habr.com/ru/post/304622/
All Articles