Back to Home

Refactoring with Claysley / Wix.com Blog

xml · scalaz · scala · refactoring

Refactoring with a Claysley composition

  • Tutorial
For quite some time, we have supported an application that processes data in XML and JSON formats. Usually support consists in fixing defects and a slight extension of functionality, but sometimes it also requires refactoring old code.


Consider, for example, a function getByPaththat extracts an element from an XML tree along 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)
  }


This function worked fine, but the requirements have changed and now we need:

  • Extract data from JSON and, possibly, other tree-like structures, and not just from XML;
  • Return error message if no data is found.

In this article, we will tell you how to refactor a function getByPathso that it meets the new requirements.

Composition Claysley


Let's highlight the piece of code that retrieves the child by name. We can name it createFunctionToExtractChildNodeByName, but let's call it simple for short child.

val child: String => XmlNode => Option[XmlNode] = name => node =>
  node.child.find(_.label == name)


Now we can make a key observation: our function getByPathis a sequential composition of functions that extract children. The compose function below implements this composition of two functions: getChildAand getChildB.

type ExtractXmlNode = XmlNode => Option[XmlNode]
def compose(getChildA: ExtractXmlNode, 
            getChildB: ExtractXmlNode): ExtractXmlNode = 
  node => for {a  <- getChildA(node); ab <- getChildB(a)} yield ab


Fortunately, the Scalaz library provides a more general, abstract way to implement composition of functions of the form A => M[A], where M is a monad . The library defines a Kleisli[M, A, B]wrapper for A => M[B]which has a> => method for implementing a sequential composition of these Kleisli, like composing ordinary functions with andThen. We will call this composition the Claysley composition . 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)


Pay attention to the pointless style we use here. Functional programmers like to write functions as compositions of other functions, without mentioning arguments.

Claysley composition is exactly what we need to implement our function getByPathas a composition of functions childthat extract child elements.

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)


Pay attention to the use of Kleisli.ask[Option, XmlNode]the fold method as a neutral element. We need this neutral element to handle the special case when path is empty. Kleisli.ask[Option, XmlNode]Is just another designation of a function from any node in Some(node).

Abstracting from XmlNode


Let's summarize our solution and abstract it from XmlNode. We can rewrite it as the following generalized function
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)

Now we can reuse it getByPathGenericto retrieve the element 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)
}


We wrote a new function, child: JValue => Option[JValue]to work with JSON instead of XML, but the function getByPathGenerichas remained unchanged and works with both XML and JSON.

Abstracting from Option


We can generalize getByPathGenericeven more and abstract it from the ScalazOption library , which provides an instance of the monad for . So we can rewrite it as follows:Option -- scalaz.Monad[Option]getByPathGeneric

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)


Now we can implement our original function getByPathusing the function getByPathGeneric:

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) 
}


Thus, we can reuse getByPathGenericto return an error message if the item is not found. For this we use scalaz. \ / (The so-called “disjunction”) which is a right-handed version scala.Either.

In addition, it Scalazprovides an “implicit” class OptionOpswith a method toRightDisjunction[B](b: B)that converts Option[A]to scalaz.B\/A, so that Some(a)becomes Right(a)and Nonebecomes Left(b).

So, we can write a function that reuses getByPathGenericto return an error message instead None, if the element to be found 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)
}


The original function getByPathprocessed only data in XML format and returned None if the element to be found was not found. We needed it to also work with the JSON format and return an error message instead of None.

We have seen how using the Claysley composition provided by the library Scalazallows you to write a generic function getByPathGenericusing parameterized types (generics) to support both XML and JSON, as well as scalaz. \ / (Disjunction) to abstract from Optionand output error messages. Wix site builder

developer , Mikhail Dagaev Original article: Wix engineers blog .


Read Next