Work with Play

Build web applications using Scala and the Play Framework.

Scala Traits Part 1

So far, we have only discussed Play-specific topics, but to use the framework at its best, it is necessary to have an in-depth knowledge of the Scala language itself.
For this reason, I will periodically cover some of the relative features and I will to start today with the first of two articles about traits.

Multiple inheritance

In a language supporting multiple inheritance, a class can have more than a parent.
The major problem with this approach is when a class C extends two superclasses having a method with the same signature, let’s call it methodX. If C does not override this method, how does the compiler know which implementation to run when methodX is invoked on an instance of C? This is known as the Diamond Problem.

Java solves the problem by not allowing multiple inheritance and replacing it with interfaces.
An interface defines an empty abstraction. A class can implement multiple interfaces but inherit only from a single parent.
This introduces the opposite problem, that is that a class has no way to re-use functionalities from multiple classes if not by composition, which requires a lot of boilerplate and it is not always very flexible.

Scala does not allow multiple inheritance either, but replaces interfaces with traits.
A trait is basically an interface with optional implementations. A class can mix in multiple traits.

Implement a trait

We’ll keep working on the UserAdmin application to implement our first trait.
In the User class, the contacts method queries the database to get the list of contacts.
We may want to optimize this behavior by caching the result between different calls. A Map[String, List[Contact]] can be used for this.
The logic to handle the cache is not specific to User but can be re-used in multiple classes: this case represents the perfect match for a trait.

Our trait, named Cache, contains a single method getOrFetch. This accepts the key of the object to return and a function to fetch it that is invoked if the desired object is not found in the cache.

app/cache/Cache.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cache

trait Cache[T] {

  private var cache: Map[String, T] = Map()

  def getOrFetch(key: String)(fetch: () => T): T = {
    cache.get(key) match {
      case Some(value) => {
        play.api.Logger.debug("getting value from cache ...")
        value
      }
      case None => {
        play.api.Logger.debug("invoking fetch callback ...")
        val value = fetch()
        cache = cache + (key -> value)
        cache.get(key).get
      }
    }
  }

}

This example uses another powerful Scala technique, called Pattern Matching, to determine whether the cache contains or not an object with the specified key.

The User class can now mix in the trait and use getOrFetch.

app/models/User.scala
1
2
3
4
5
6
7
8
case class User(id: Pk[Int], username: String, age: Int) extends cache.Cache[List[Contact]] {

  def contacts: List[Contact] = getOrFetch("contacts"){ () =>
    id.map { userId =>
    Contact.loadByUserId(userId)
    }.getOrElse(Nil)
  }
}

You need to use the extends keyword for the parent or the first trait, like in this case, and with for all remaining traits. We’ll see this in the following examples.

To verify that our trait works correctly, let’s launch a REPL from the project’s folder by running play console.
I will cover in details how this works in a future article, but for now let’s just execute the following commands.

1
2
3
4
5
6
7
scala> import play.core.StaticApplication
import play.core.StaticApplication

scala> new StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:mysql://localhost:3306/user_admin?useUnicode=true&characterEncoding=UTF-8
[info] play - Application started (Prod)
res0: play.core.StaticApplication = play.core.StaticApplication@55f26a0e

This starts a full Play application accessing the database configured in conf/application.conf.
Let’s now load a User from the database:

1
2
3
4
5
scala> import models._
import models._

scala> val user = User.list.head
user: models.User = User(1,diego,20)

Calling user.contacts the first time should hit the database because the cache is empty.

1
2
3
scala> user.contacts
[debug] application - invoking fetch callback ...
res1: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

A second call will instead hit the cache.

1
2
3
scala> user.contacts
[debug] application - getting value from cache ...
res2: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

Composition through inheritance

While the new version of User should have better performances, it makes it impossible to ever get a fresh value after the first access, which is quite restrictive.
It may sound contradictory, but we can use inheritance to obtain better composition and solve this problem.
Let’s roll-back User to the old cache-less version and implement a subclass for it.

app/models/User.scala
1
2
3
4
5
class UserWithCachedContacts(id: Pk[Int], username: String, age: Int)
  extends User(id, username, age) with cache.Cache[List[Contact]] {

  override def contacts: List[Contact] = getOrFetch("contacts")(() => super.contacts)
}

This time we are using the with keyword because UserWithCachedContacts inherits from a parent class.

I have modified the User companion object to include a method named loadWithCache which returns instances of this last class. You’ll find the code for it in the source linked at the end of the article.

Apply traits to single instances

It is not really necessary to declare a new class to compose User with Cache. In fact, Scala allows to mix in traits to single instances at creation time.

app/models/User.scala
1
2
3
new User(id, username, age) with cache.Cache[List[Contact]] {
  override def contacts: List[Contact] = getOrFetch("contacts")(() => super.contacts)
}

In these cases you can use the with keyword also for the first trait.
It is possible to mixin a trait to a single object only at instantiation time. This is because the JVM needs to allocate the relative space on the heap. For this reason we invoked the constructor and not the factory method from the companion object.

Traits can extend other traits

A class can extend one or more traits, but the same can do a trait. Actually, a trait can even extend a class.
Suppose we want to implement some mechanism to refresh an element in the cache if a certain period of time has passed from the last access.
We could do this in the Cache trait but, again, we would change its behavior permanently without having the possibility to use a non-expiring cache.
A better way is to have a separate trait extending the one we already have.

app/cache/Cache.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trait Cache[T]  {

   ...
}

trait ExpiryCache[T] extends Cache[T] {

  val timeout: Long = 5000

  var lastAccess: Map[String, Long] = Map()

  override def getOrFetch(key: String)(fetch: () => T): T = {
    if (lastAccess.contains(key) && System.currentTimeMillis - lastAccess(key) > timeout) {
      play.api.Logger.debug("Timeout expired, clearing the cache ...")
      clear(key)
    }
    lastAccess = lastAccess + (key -> System.currentTimeMillis)
    super.getOrFetch(key)(fetch)
  }
}

We added a method clear to Cache to remove a specific entry from the cache.
ExpiryCache extends Cache and invokes the relative methods. To call the parent’s version of getOrFetch it has to use the prefix super, this is not necessary for clear because ExpiryCache does not override it, so the compiler has no doubt about which version of the method to execute.
One last note: the timeout in ExpiryCache is set to 5 seconds.

Our anonymous class can now extend ExpiryCache.

app/models/User.scala
1
2
3
new User(id, username, age) with cache.ExpiryCache[List[Contact]] {
      override def contacts: List[Contact] = getOrFetch("contacts")(() => super.contacts)
}

Let’s test these changes in a new REPL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> import play.core.StaticApplication
import play.core.StaticApplication

scala> new StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:mysql://localhost:3306/user_admin?useUnicode=true&characterEncoding=UTF-8
[info] play - Application started (Prod)
res0: play.core.StaticApplication = play.core.StaticApplication@55f26a0e

scala> import play.core.StaticApplication
import play.core.StaticApplication

scala> new StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:mysql://localhost:3306/user_admin?useUnicode=true&characterEncoding=UTF-8
[info] play - Application started (Prod)
res0: play.core.StaticApplication = play.core.StaticApplication@55f26a0e

Like mentioned above, User#loadWithCache creates a User with ExpiryCache.
Let’s call two times User#contacts with an interval longer than five seconds.

1
2
3
4
5
6
7
8
scala> user.contacts
[debug] application - invoking fetch callback ...
res1: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

scala> user.contacts
[debug] application - Timeout expired, clearing the cache ...
[debug] application - invoking fetch callback ...
res2: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

Since the second call happened more than 5 seconds after the first, the cache was cleared and the fetch callback called again.
If the interval is shorter than 5 seconds, the value from the cache is returned.

1
2
3
4
5
6
7
scala> user.contacts
[debug] application - invoking fetch callback ...
res1: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

scala> user.contacts
[debug] application - getting value from cache ...
res3: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

Stack-able traits

Suppose we want to use a different cache implementation, for example one keeping just a single element.
How can we re-use the logic in ExpiryCache, so that we can mix it in any other cache implementation without any copy/paste? This is where stack-able traits come in.
First step is to define a more abstract Cache with no implementation and letting the different caches extend it.

app/cache/Cache.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
trait Cache[T] {

  def getOrFetch(key: String)(fetch: () => T): T

  def clear(key: String)
}

trait MapCache[T] extends Cache[T] {

  ...
}

trait SingleElementCache[T] extends Cache[T] {
  private var cacheKey: String = null
  private var cacheValue: T = null.asInstanceOf[T]

  override def getOrFetch(key: String)(fetch: () => T): T = {
    if (cacheKey == key) {
      play.api.Logger.debug("single element cache hit ...")
      cacheValue
    } else {
      play.api.Logger.debug("single element cache miss, invoking the callback ...")
      val result = fetch()
      cacheValue = result
      cacheKey = key
      result
    }
  }

  override def clear(key: String) = {
    cacheKey = null
  }
}

The old Cache has been renamed to MapCache. The new one contains just the method signatures.
At this point, ExpiryCache could extend Cache as well but we are going to get a compilation error when trying to do so.

1
method clear in trait Cache is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'    Cache.scala /UserAdmin/app/cache    line 64 Scala Problem

This make sense as Cache, the parent of ExpiryCache, does not contain any implementation.
Let’s change it, then.

app/cache/Cache.scala
1
2
3
4
5
6
7
8
9
trait Cache[T] {

  def getOrFetch(key: String)(fetch: () => T): T = {
    play.api.Logger.debug("Default empty cache always invoking the callback...")
    fetch()
  }

  def clear(key: String) {}
}

No compilation error anymore when making ExpiryCache inherit from Cache.

Time to test. Let’s start with ExpiryCache only.

1
2
3
4
5
6
7
8
9
10
11
12
scala> user.contacts
[debug] application - Default empty cache always invoking the callback...
res1: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))
# wait for more than 5 seconds
scala> user.contacts
[debug] application - Timeout expired, clearing the cache ...
[debug] application - Default empty cache always invoking the callback...
res2: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))
# wait for less than 5 seconds
scala> user.contacts
[debug] application - Default empty cache always invoking the callback...
res2: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

Nothing surprising here, ExpiryCache keeps working as expected and invokes the methods in the parent trait, Cache.

Things get more interesting if more than one trait are mixed in.

app/models/User.scala
1
2
3
new User(id, username, age) with cache.SingleElementCache[List[Contact]] with cache.ExpiryCache[List[Contact]]{
      override def contacts: List[Contact] = getOrFetch("contacts")(() => super.contacts)
}

This is the output from the console.

1
2
3
4
5
6
7
8
9
10
11
12
scala> user.contacts
[debug] application - single element cache miss, invoking the callback ...
res1: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))
# wait for more than 5 seconds
scala> user.contacts
[debug] application - Timeout expired, clearing the cache ...
[debug] application - single element cache miss, invoking the callback ...
res2: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))
# wait for less than 5 seconds
scala> user.contacts
[debug] application - single element cache hit ...
res3: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

This time, when super.getOrFetch and super.clear are called wihin ExpiryCache, they result in an invokation to SingleElementCache’s implementation. This is because trait’s parents can be overridden at the same way you normally override methods. In this case SingleElementCache preceeds ExpiryCache and becomes it’s new parent. Traits can be stack one on top of the other to dynamically obtain different combinations and behaviors.

In this last example ExpiryCache is stacjed on top of MapCache.

app/models/User.scala
1
2
3
new User(id, username, age) with cache.MapCache[List[Contact]] with cache.ExpiryCache[List[Contact]]{
      override def contacts: List[Contact] = getOrFetch("contacts")(() => super.contacts)
}

And this is the output from the REPL:

1
2
3
4
5
6
7
8
9
10
11
12
scala> user.contacts
[debug] application - invoking fetch callback ...
res1: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))
# wait for more than 5 seconds
scala> user.contacts
[debug] application - Timeout expired, clearing the cache ...
[debug] application - invoking fetch callback ...
res2: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))
# wait for less than 5 seconds
scala> user.contacts
[debug] application - getting value from cache ...
res3: List[models.Contact] = List(Contact(1,email,diegocastorina@gmail.com))

Conclusions

Traits are one of the most powerful features in Scala as they permit a class to inherit functionalities from multiple other classes without having to inherit from them.
In the next article we are going to continue talk about traits but let’s conclude this part with the link to the source on Github.

Comments