Chapter 11. Functional Design Patterns – Applying What We Learned

We have already come a long way in Scala and in learning about the various design patterns in the language. Now, you should be at a stage where you are confident about when to use specific design patterns and when to avoid them. You saw some of the specifics and nice features of Scala that lead to its expressiveness. We went through the Gang of Four design patterns as well as some important functional programming concepts such as monads. Throughout the book, we have tried to keep mathematics theories to a really basic level, and we have tried to avoid some scary Greek letters in formulas that are hard to understand by non-mathematicians, who may also want to use a functional programming language to its full potential.

The aim of this and the next chapter is to look at Scala from a more practical point of view. Knowing about a language and some design patterns is not always enough for a developer to see the whole picture and the potential of the language possibilities. In this chapter, we will show how some of the concepts we presented before can be combined together to write even more powerful and cleaner programs. We will look at the following topics:

  • Lens design pattern
  • Cake design pattern
  • Pimp my library
  • Stackable traits
  • Type class design pattern
  • Lazy evaluation
  • Partial functions
  • Implicit injection
  • Duck typing
  • Memoization

Some of the sections in this chapter will show the concepts that we haven't seen before. Others will combine some of the features of Scala and the design patterns we have learned so far in order to achieve something else. In all the cases though, these concepts will deal with either a specific language feature or a limitation we have already seen, or help in achieving something commonly seen in actual software engineering projects.

The lens design pattern

We already mentioned before that, in Scala, objects are immutable. You can, of course, make sure that a specific class has its fields declared as vars but this is discouraged and considered bad practice. After all, immutability is good and we should try to aim for it.

The lens design pattern was created specifically for that purpose and allows us to overcome the immutability limitation and at the same time preserve the code readability. In the following subsections, we will start with some code that doesn't use the lens design pattern and we will go step by step to show how to use it and how it improves our applications.

Lens example

In order to show the lens design pattern in practice, we will create a class hierarchy that is usually seen in enterprise applications. Let's imagine that we are building a system for a library that can be used by the employees of different companies. We might end up with the following classes:

case class Country(name: String, code: String)
case class City(name: String, country: Country)
case class Address(number: Int, street: String, city: City)
case class Company(name: String, address: Address)
case class User(name: String, company: Company, address: Address)

The representation of these classes as a class diagram would look like the following figure:

Lens example

The diagram is pretty clear and it doesn't need too much explanation. We basically have a User class that has other information about the user. Other classes contain others and so on. There is absolutely no challenge in using our classes if we don't want to be able to modify anything. However, as soon as we go on to modify something, it becomes complicated.

Without the lens design pattern

In this subsection, we will see how to use our classes if we want to modify some of their properties.

Immutable and verbose

Without getting into too much details, let's see how an example application would look like:

object UserVerboseExample {
  def main(args: Array[String]): Unit = {
    val uk = Country("United Kingdom", "uk")
    val london = City("London", uk)
    val buckinghamPalace = Address(1, "Buckingham Palace Road", london)
    val castleBuilders = Company("Castle Builders", buckinghamPalace)
    
    val switzerland = Country("Switzerland", "CH")
    val geneva = City("geneva", switzerland)
    val genevaAddress = Address(1, "Geneva Lake", geneva)
    
    val ivan = User("Ivan", castleBuilders, genevaAddress)
    System.out.println(ivan)
    
    System.out.println("Capitalize UK code...")

    val ivanFixed = ivan.copy(
      company = ivan.company.copy(
        address = ivan.company.address.copy(
          city = ivan.company.address.city.copy(
            country = ivan.company.address.city.country.copy(
              code = ivan.company.address.city.country.code.toUpperCase
            )
          )
        )
      )
    )
    System.out.println(ivanFixed)
  }
}

The preceding application creates one user for our library and then decides to change the company country code, as we initially created it in lowercase characters. The output of the application looks like this:

Immutable and verbose

Our application works correctly but as you can see in the highlighted code, it is extremely verbose and long and making a mistake is really easy. We don't want to write code like this, as it will be hard to maintain and change in the future.

Using mutable properties

The first thought that might come into your head is to change the classes and make the properties variable. Here is how our case classes would change:

case class Country(var name: String, var code: String)
case class City(var name: String, var country: Country)
case class Address(var number: Int, var street: String, var city: City)
case class Company(var name: String, var address: Address)
case class User(var name: String, var company: Company, var address: Address)

After this, using these classes will be as easy as this:

object UserBadExample {
  def main(args: Array[String]): Unit = {
    val uk = Country("United Kingdom", "uk")
    val london = City("London", uk)
    val buckinghamPalace = Address(1, "Buckingham Palace Road", london)

    val castleBuilders = Company("Castle Builders", buckinghamPalace)

    val switzerland = Country("Switzerland", "CH")
    val geneva = City("geneva", switzerland)
    val genevaAddress = Address(1, "Geneva Lake", geneva)

    val ivan = User("Ivan", castleBuilders, genevaAddress)
    System.out.println(ivan)

    System.out.println("Capitalize UK code...")

    ivan.company.address.city.country.code = ivan.company.address.city.country.code.toUpperCase
    System.out.println(ivan)
  }
}

Note

In the preceding code example, we could have also changed the country code using the following: uk.code = uk.code.toUpperCase. This would work, because we use a reference of the country in our User object.

The preceding example will produce absolutely the same output. However, here we broke the rule that everything in Scala is immutable. This might not look like a big deal in the current example, but in reality, it goes against the Scala principles. This is considered bad code and we should try to avoid it.

With the lens design pattern

In the previous subsection, we saw how complicated something such as changing one property of a nested class can be. We are going after nice, clean, and correct code, and we also don't want to go against the principles of Scala.

Luckily for us, cases such as the one we just mentioned previously are exactly the reason for the creation of the lens design pattern. In this chapter, we will see the Scalaz library for the first time in this book. It defines many functional programming abstractions for us, and we can use them straight away easily without worrying whether they follow some specific set of rules or not.

So what are lenses anyway? We won't get too deep into the theoretical aspects here, as this is out of the scope of this book. It is enough for us to know for what they are used for, and if you want to know more, there is plenty of material on lenses, store, and comonads online, which can make these concepts clearer. A simple way to represent a lens is the following:

case class Lens[X, Y](get: X => Y, set: (X, Y) => X)

This basically lets us get and set different properties of an object of the X type. This means that, in our case, we will have to define different lenses for the different properties we want to set:

object User {
  val userCompany = Lens.lensu[User, Company](
    (u, company) => u.copy(company = company),
    _.company
  )
  
  val userAddress = Lens.lensu[User, Address](
    (u, address) => u.copy(address = address),
    _.address
  )
  
  val companyAddress = Lens.lensu[Company, Address](
    (c, address) => c.copy(address = address),
    _.address
  )
  
  val addressCity = Lens.lensu[Address, City](
    (a, city) => a.copy(city = city),
    _.city
  )
  
  val cityCountry = Lens.lensu[City, Country](
    (c, country) => c.copy(country = country),
    _.country
  )
  
  val countryCode = Lens.lensu[Country, String](
    (c, code) => c.copy(code = code),
    _.code
  )
  
  val userCompanyCountryCode = userCompany >=> companyAddress >=> addressCity >=> cityCountry >=> countryCode
}

The preceding code is a companion object to our User class. There are a lot of things going on here, so we will explain this. You can see the calls to Lens.lensu[A, B]. They create actual lenses that for an object of the A type, the calls get and set a value of the B type. There is nothing special about them really, and they simply look like a boiler plate code. The interesting part here is the highlighted code—it uses the >=> operator, which is an alias for andThen. This allows us to compose lenses and this is exactly what we will do. We will define a composition that allows us to go from a User object through the chain and set the country code of the country of the Company. We could have used compose as well, which has an alias of <=< because andThen internally calls compose and it would look like the following:

val userCompanyCountryCodeCompose = countryCode <=< cityCountry <=< addressCity <=< companyAddress <=< userCompany

The latter, however, is not as intuitive as the former. Using our lens is now very easy. We need to make sure to import our companion object and then we can simply use the following code where we change the country code to upper case:

val ivanFixed = userCompanyCountryCode.mod(_.toUpperCase, ivan)

You saw how the lens design pattern allows us to cleanly set properties of our case class without breaking the immutability rule. We simply need to define the right lenses and then use them.

Minimizing the boilerplate

The preceding example showed quite a lot of boilerplate code. It is not complicated but it requires us to write quite a lot of extra stuff, and then any refactoring will likely affect these manually defined lenses. There has been an effort in creating libraries that automatically generate lenses for all user-defined classes that can then be used easily. One example of a library that seems to be maintained well is Monocle: https://github.com/julien-truffaut/Monocle. It is documented well and can be used so that we don't have to write any boilerplate code. It has its limitations though and the user should make sure they are okay with what the library provides. It also provides other optics concepts that could be useful.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.123.155