DRY Code with Higher Order Function in Scala – Part II
This post is part II in my series about Higher order function in Scala.
You can find part I here
In this post I am going to introduce:
Higher-order function with generics type
Hope you enjoy!
Higher-order function with generics type
With generics type, Scala give us an opportunity to reduce the duplicated code.
Let’s look at the bellow example:
We have resultSet is data which is object from Infrastructure, now we need to convert it to a model in Domain layer.
val countries = resultSet.stringOpt("geo_locations_countries").fold(Seq.empty[Country])(Json.parse(_).as[Seq[Country]])
val cities = resultSet.stringOpt("geo_locations_cities").fold(Seq.empty[City])(Json.parse(_).as[Seq[City]])
val regions = resultSet.stringOpt("geo_locations_regions").fold(Seq.empty[Region])(Json.parse(_).as[Seq[Region]])
val zips = resultSet.stringOpt("geo_locations_zips").fold(Seq.empty[Zip])(Json.parse(_).as[Seq[Zip]])
It will look up for a string (the key of a map), if it hasn’t any value, we will return an empty Seq. If not we will parse this json value to a model.
It’s quite good but it’s seem not DRY.
Let’s use generic type in this case.
Firstly, we can see the similar logic here is we take a string then parse the json value to Seq of some kind of Model (Country or City or Region or Zip).
Instead of do the same thing all the time, we can call it as type T for abstraction and work with this type.
The function was defined as bellow:
I used implicit value because I don’t wanna pass it into every function and we need a Reads type to working with json format.
Now we can call it to get the Model value here:
implicit val wrs: WrappedResultSet = resultSet
val countries = parse[Country]("geo_locations_countries")
val cities = parse[City]("geo_locations_cities")
val regions = parse[Region]("geo_locations_regions")
val zips = parse[Zip]("geo_locations_zips")
It’s shorter and not too hard for reader.
The important thing we care about is just the sequence of Country converted from data of Infra’s object, for example.
But now we have one more step before we can get the Model object.
We need to parse json value to some type, String or Int firstly.
val locations = resultSet.stringOpt("geo_locations").fold(Seq.empty[Location])(Json.parse(_).as[Seq[String]].map(Location))
val locales = resultSet.stringOpt("locales").fold(Seq.empty[Locale])(Json.parse(_).as[Seq[Int]].map(Locale))
Ok, it’s time to use higher-order function. We just need to pass it into our function here
Then our source code much more readable like this:
val locations = parseThenMap[String, Location]("geo_locations")(Location(_))
val locales = parseThenMap[Int, Locale]("locales")(Locale(_))
The Higher-order function above is quite complex, it’s not easy for beginner at the first time.
So the more flexible function we implemented the more unreadability source we have.
You can custumize yourself parseThenMap or parseThenFilter or parseThenStore any function that you want.
Higher-order function are useful but the complexity of function depends on the context and our team’s convention.
After refactoring, we can see the total different.
One of the feature in Functional Programming that I love the most is composition.
We can combine any pure function into one function very smoothly, it helps us to just need to focus on function in our scope then after that we can easily composite several function to solve our problem.
I recommend you guys to read this article Composing predicates.
The author explained very clear about Function composition.
These function are very interesting for me:
def complement[A](predicate: A => Boolean) = (a: A) => !predicate(a)
def any[A](predicates: (A => Boolean)*): A => Boolean =
a => predicates.exists(pred => pred(a))
def none[A](predicates: (A => Boolean)*): A => Boolean =
def every[A](predicates: (A => Boolean)*): A => Boolean =
I tried to use its
def isMultiOf(x: Int, y: Int): Boolean = x % y == 0
def isMultiOfThree(x: Int): Boolean = isMultiOf(x, 3)
def isMultiOfFive(x: Int): Boolean = isMultiOf(x, 5)
def isMultiOfSeven(x: Int): Boolean = isMultiOf(x, 7)
// Without composition
// Get all number which is multiples of 3 or 5 or 7
// Hard to adding or changing the statement logic
list.filter(x => isMultiOfThree(x) || isMultiOfFive(x) || isMultiOfSeven(x))
// Composition function
// More easy to expanding or changing logic
def orSpec: Int => Boolean = any(isMultiOfThree, isMultiOfFive, isMultiOfSeven)
def norSpec: Int => Boolean = none(isMultiOfThree, isMultiOfFive)
def andSpec: Int => Boolean = every(isMultiOfFive, isMultiOfSeven)
I think these function very useful while working with any collection and predicates.
It helps our source code more extendable and readable.
Encapsulating common patterns of design into functional forms called higher order functions.
These functions not only shortens programs, but also produce clearer programs because the intended meaning of the program is explicitly rather than implicitly stated.
It is much easier to understand the program and the intention of the programmer is clearly expressed in the code.
Functions which take functions as arguments are much easier to re-use than other functions.
Using higher-order function help us save a lot of time because we don’t repeat ourself and our source code is more maintainable.