Memoization of method results
scala> import scalacache._
import scalacache._
scala> import scalacache.memcached._
import scalacache.memcached._
scala> import scalacache.memoization._
import scalacache.memoization._
scala> import scalacache.serialization.binary._
import scalacache.serialization.binary._
scala> import scalacache.modes.try_._
import scalacache.modes.try_._
scala> import scala.concurrent.duration._
import scala.concurrent.duration._
scala> import scala.util.Try
import scala.util.Try
scala> final case class Cat(id: Int, name: String, colour: String)
defined class Cat
scala> implicit val catsCache: Cache[Cat] = MemcachedCache("localhost:11211")
catsCache: scalacache.Cache[Cat] = scalacache.memcached.MemcachedCache@6a9e8a0c
scala> // You wouldn't normally need to specify the type params for memoize.
| // This is an artifact of the way this README is generated using tut.
| def getCat(id: Int): Try[Cat] = memoize[Try, Cat](Some(10.seconds)) {
| // Retrieve data from a remote API here ...
| Cat(id, s"cat ${id}", "black")
| }
getCat: (id: Int)scala.util.Try[Cat]
scala> getCat(123)
res2: scala.util.Try[Cat] = Success(Cat(123,cat 123,black))
Did you spot the magic word ‘memoize’ in the getCat
method? Just adding this keyword will cause the result of the method to be memoized to a cache.
The next time you call the method with the same arguments the result will be retrieved from the cache and returned immediately.
If the result of your block is wrapped in an effect container, use memoizeF
:
scala> def getCatF(id: Int): Try[Cat] = memoizeF[Try, Cat](Some(10.seconds)) {
| Try {
| // Retrieve data from a remote API here ...
| Cat(id, s"cat ${id}", "black")
| }
| }
getCatF: (id: Int)scala.util.Try[Cat]
scala> getCatF(123)
res3: scala.util.Try[Cat] = Success(Cat(123,cat 123,black))
Synchronous memoization API
Again, there is a synchronous memoization method for convient use of the synchronous mode:
scala> import scalacache.modes.sync._
import scalacache.modes.sync._
scala> def getCatSync(id: Int): Cat = memoizeSync(Some(10.seconds)) {
| // Do DB lookup here ...
| Cat(id, s"cat ${id}", "black")
| }
getCatSync: (id: Int)Cat
scala> getCatSync(123)
res4: Cat = Cat(123,cat 123,black)
How it works
memoize
automatically builds a cache key based on the method being called, and the values of the arguments being passed to that method.
Under the hood it makes use of Scala macros, so most of the information needed to build the cache key is gathered at compile time. No reflection or AOP magic is required at runtime.
Cache key generation
The cache key is built automatically from the class name, the name of the enclosing method, and the values of all of the method’s parameters.
For example, given the following method:
package foo
object Bar {
def baz(a: Int, b: String)(c: String): Int = memoizeSync(None) {
// Reticulating splines...
123
}
}
the result of the method call
val result = Bar.baz(1, "hello")("world")
would be cached with the key: foo.bar.Baz(1, hello)(world)
.
Note that the cache key generation logic is customizable. Just provide your own implementation of MethodCallToStringConverter
Enclosing class’s constructor arguments
If your memoized method is inside a class, rather than an object, then the method’s result might depend on values passed to that class’s constructor.
For example, if your code looks like this:
package foo
class Bar(a: Int) {
def baz(b: Int): Int = memoizeSync(None) {
a + b
}
}
then you want the cache key to depend on the values of both a
and b
. In that case, you need to use a different implementation of MethodCallToStringConverter, like this:
implicit val cacheConfig = CacheConfig(
memoization = MemoizationConfig(MethodCallToStringConverter.includeClassConstructorParams)
)
Doing this will ensure that both the constructor arguments and the method arguments are included in the cache key:
new Bar(10).baz(42) // cached as "foo.Bar(10).baz(42)" -> 52
new Bar(20).baz(42) // cached as "foo.Bar(20).baz(42)" -> 62
Excluding parameters from the generated cache key
If there are any parameters (either method arguments or class constructor arguments) that you don’t want to include in the auto-generated cache key for memoization, you can exclude them using the @cacheKeyExclude
annotation.
For example:
def doSomething(userId: UserId)(implicit @cacheKeyExclude db: DBConnection): String = memoize {
...
}
will only include the userId
argument’s value in its cache keys.