Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refs #35: Custom generic serialisation via type class #86

Merged
merged 8 commits into from
Apr 3, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package scalacache.caffeine

import scalacache.serdes.Codec
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can we make the package scalacache.serialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool done.

import scalacache.{ LoggingSupport, Cache, Entry }
import com.github.benmanes.caffeine.cache.{ Cache => CCache, Caffeine }

Expand All @@ -23,11 +24,12 @@ class CaffeineCache(underlying: CCache[String, Object])

/**
* Get the value corresponding to the given key from the cache
*
* @param key cache key
* @tparam V the type of the corresponding value
* @return the value, if there is one
*/
override def get[V](key: String) = {
override def get[V: Codec](key: String) = {
val entry = Option(underlying.getIfPresent(key).asInstanceOf[Entry[V]])
/*
Note: we could delete the entry from the cache if it has expired,
Expand All @@ -44,12 +46,13 @@ class CaffeineCache(underlying: CCache[String, Object])

/**
* Insert the given key-value pair into the cache, with an optional Time To Live.
*
* @param key cache key
* @param value corresponding value
* @param ttl Time To Live
* @tparam V the type of the corresponding value
*/
override def put[V](key: String, value: V, ttl: Option[Duration] = None) = {
override def put[V: Codec](key: String, value: V, ttl: Option[Duration] = None) = {
val entry = Entry(value, ttl.map(toExpiryTime))
underlying.put(key, entry.asInstanceOf[Object])
logCachePut(key, ttl)
Expand All @@ -59,6 +62,7 @@ class CaffeineCache(underlying: CCache[String, Object])
/**
* Remove the given key and its associated value from the cache, if it exists.
* If the key is not in the cache, do nothing.
*
* @param key cache key
*/
override def remove(key: String) = Future.successful(underlying.invalidate(key))
Expand All @@ -82,6 +86,7 @@ object CaffeineCache {

/**
* Create a new cache utilizing the given underlying Caffeine cache.
*
* @param underlying a Caffeine cache
*/
def apply(underlying: CCache[String, Object]): CaffeineCache = new CaffeineCache(underlying)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ class CaffeineCacheSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter
val underlying = newCCache
val entry = Entry("hello", expiresAt = None)
underlying.put("key1", entry)
whenReady(CaffeineCache(underlying).get("key1")) { result =>
whenReady(CaffeineCache(underlying).get[String]("key1")) { result =>
result should be(Some("hello"))
}
}

it should "return None if the given key does not exist in the underlying cache" in {
val underlying = newCCache
whenReady(CaffeineCache(underlying).get("non-existent key")) { result =>
whenReady(CaffeineCache(underlying).get[String]("non-existent key")) { result =>
result should be(None)
}
}
Expand All @@ -33,7 +33,7 @@ class CaffeineCacheSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter
val underlying = newCCache
val expiredEntry = Entry("hello", expiresAt = Some(DateTime.now.minusSeconds(1)))
underlying.put("key1", expiredEntry)
whenReady(CaffeineCache(underlying).get("non-existent key")) { result =>
whenReady(CaffeineCache(underlying).get[String]("non-existent key")) { result =>
result should be(None)
}
}
Expand Down
8 changes: 6 additions & 2 deletions core/src/main/scala/scalacache/Cache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,33 @@ package scalacache

import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scalacache.serdes.Codec

trait Cache {

/**
* Get the value corresponding to the given key from the cache
*
* @param key cache key
* @tparam V the type of the corresponding value
* @return the value, if there is one
*/
def get[V](key: String): Future[Option[V]]
def get[V: Codec](key: String): Future[Option[V]]

/**
* Insert the given key-value pair into the cache, with an optional Time To Live.
*
* @param key cache key
* @param value corresponding value
* @param ttl Time To Live
* @tparam V the type of the corresponding value
*/
def put[V](key: String, value: V, ttl: Option[Duration]): Future[Unit]
def put[V: Codec](key: String, value: V, ttl: Option[Duration]): Future[Unit]

/**
* Remove the given key and its associated value from the cache, if it exists.
* If the key is not in the cache, do nothing.
*
* @param key cache key
*/
def remove(key: String): Future[Unit]
Expand Down
25 changes: 13 additions & 12 deletions core/src/main/scala/scalacache/memoization/Macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.concurrent.{ ExecutionContext, Future }
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
import scala.concurrent.duration.Duration
import scalacache.serdes.Codec
import scalacache.{ Flags, ScalaCache }

class Macros(val c: blackbox.Context) {
Expand All @@ -13,39 +14,39 @@ class Macros(val c: blackbox.Context) {
We get weird macro compilation errors if we write `f: c.Expr[Future[A]]`, so we'll cheat and just make it a `c.Tree`.
I think this is a macros bug.
*/
def memoizeImpl[A: c.WeakTypeTag](f: c.Tree)(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext]): Tree = {
def memoizeImpl[A: c.WeakTypeTag](f: c.Tree)(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext], codec: c.Expr[Codec[A]]): Tree = {
commonMacroImpl(scalaCache, { keyName =>
q"""_root_.scalacache.caching($keyName)($f)($scalaCache, $flags, $ec)"""
q"""_root_.scalacache.caching($keyName)($f)($scalaCache, $flags, $ec, $codec)"""
})
}

def memoizeImplWithTTL[A: c.WeakTypeTag](ttl: c.Expr[Duration])(f: c.Tree)(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext]): Tree = {
def memoizeImplWithTTL[A: c.WeakTypeTag](ttl: c.Expr[Duration])(f: c.Tree)(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext], codec: c.Expr[Codec[A]]): Tree = {
commonMacroImpl(scalaCache, { keyName =>
q"""_root_.scalacache.cachingWithTTL($keyName)($ttl)($f)($scalaCache, $flags, $ec)"""
q"""_root_.scalacache.cachingWithTTL($keyName)($ttl)($f)($scalaCache, $flags, $ec, $codec)"""
})
}

def memoizeImplWithOptionalTTL[A: c.WeakTypeTag](optionalTtl: c.Expr[Option[Duration]])(f: c.Tree)(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext]): Tree = {
def memoizeImplWithOptionalTTL[A: c.WeakTypeTag](optionalTtl: c.Expr[Option[Duration]])(f: c.Tree)(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext], codec: c.Expr[Codec[A]]): Tree = {
commonMacroImpl(scalaCache, { keyName =>
q"""_root_.scalacache.cachingWithOptionalTTL($keyName)($optionalTtl)($f)($scalaCache, $flags, $ec)"""
q"""_root_.scalacache.cachingWithOptionalTTL($keyName)($optionalTtl)($f)($scalaCache, $flags, $ec, $codec)"""
})
}

def memoizeSyncImpl[A: c.WeakTypeTag](f: c.Expr[A])(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags]): Tree = {
def memoizeSyncImpl[A: c.WeakTypeTag](f: c.Expr[A])(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], codec: c.Expr[Codec[A]]): Tree = {
commonMacroImpl(scalaCache, { keyName =>
q"""_root_.scalacache.sync.caching($keyName)($f)($scalaCache, $flags)"""
q"""_root_.scalacache.sync.caching($keyName)($f)($scalaCache, $flags, $codec)"""
})
}

def memoizeSyncImplWithTTL[A: c.WeakTypeTag](ttl: c.Expr[Duration])(f: c.Expr[A])(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags]): Tree = {
def memoizeSyncImplWithTTL[A: c.WeakTypeTag](ttl: c.Expr[Duration])(f: c.Expr[A])(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], codec: c.Expr[Codec[A]]): Tree = {
commonMacroImpl(scalaCache, { keyName =>
q"""_root_.scalacache.sync.cachingWithTTL($keyName)($ttl)($f)($scalaCache, $flags)"""
q"""_root_.scalacache.sync.cachingWithTTL($keyName)($ttl)($f)($scalaCache, $flags, $codec)"""
})
}

def memoizeSyncImplWithOptionalTTL[A: c.WeakTypeTag](optionalTtl: c.Expr[Option[Duration]])(f: c.Expr[A])(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags]): Tree = {
def memoizeSyncImplWithOptionalTTL[A: c.WeakTypeTag](optionalTtl: c.Expr[Option[Duration]])(f: c.Expr[A])(scalaCache: c.Expr[ScalaCache], flags: c.Expr[Flags], codec: c.Expr[Codec[A]]): Tree = {
commonMacroImpl(scalaCache, { keyName =>
q"""_root_.scalacache.sync.cachingWithOptionalTTL($keyName)($optionalTtl)($f)($scalaCache, $flags)"""
q"""_root_.scalacache.sync.cachingWithOptionalTTL($keyName)($optionalTtl)($f)($scalaCache, $flags, $codec)"""
})
}

Expand Down
13 changes: 7 additions & 6 deletions core/src/main/scala/scalacache/memoization/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scalacache
import scala.concurrent.{ ExecutionContext, Future }
import scala.language.experimental.macros
import scala.concurrent.duration._
import scalacache.serdes.Codec

/**
* Utilities for memoizing the results of method calls in a cache.
Expand Down Expand Up @@ -30,7 +31,7 @@ package object memoization {
* @tparam A type of the value to be cached
* @return a Future of the result, either retrieved from the cache or calculated by executing the function `f`
*/
def memoize[A](f: => Future[A])(implicit scalaCache: ScalaCache, flags: Flags, ec: ExecutionContext): Future[A] = macro Macros.memoizeImpl[A]
def memoize[A](f: => Future[A])(implicit scalaCache: ScalaCache, flags: Flags, ec: ExecutionContext, codec: Codec[A]): Future[A] = macro Macros.memoizeImpl[A]

/**
* Perform the given operation and memoize its result to a cache before returning it.
Expand All @@ -55,7 +56,7 @@ package object memoization {
* @tparam A type of the value to be cached
* @return a Future of the result, either retrieved from the cache or calculated by executing the function `f`
*/
def memoize[A](ttl: Duration)(f: => Future[A])(implicit scalaCache: ScalaCache, flags: Flags, ec: ExecutionContext): Future[A] = macro Macros.memoizeImplWithTTL[A]
def memoize[A](ttl: Duration)(f: => Future[A])(implicit scalaCache: ScalaCache, flags: Flags, ec: ExecutionContext, codec: Codec[A]): Future[A] = macro Macros.memoizeImplWithTTL[A]

/**
* Perform the given operation and memoize its result to a cache before returning it.
Expand All @@ -79,7 +80,7 @@ package object memoization {
* @tparam A type of the value to be cached
* @return the result, either retrieved from the cache or calculated by executing the function `f`
*/
def memoize[A](optionalTtl: Option[Duration])(f: => Future[A])(implicit scalaCache: ScalaCache, flags: Flags, ec: ExecutionContext): Future[A] = macro Macros.memoizeImplWithOptionalTTL[A]
def memoize[A](optionalTtl: Option[Duration])(f: => Future[A])(implicit scalaCache: ScalaCache, flags: Flags, ec: ExecutionContext, codec: Codec[A]): Future[A] = macro Macros.memoizeImplWithOptionalTTL[A]

/**
* Perform the given operation and memoize its result to a cache before returning it.
Expand All @@ -95,7 +96,7 @@ package object memoization {
* @tparam A type of the value to be cached
* @return the result, either retrieved from the cache or calculated by executing the function `f`
*/
def memoizeSync[A](f: => A)(implicit scalaCache: ScalaCache, flags: Flags): A = macro Macros.memoizeSyncImpl[A]
def memoizeSync[A](f: => A)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[A]): A = macro Macros.memoizeSyncImpl[A]

/**
* Perform the given operation and memoize its result to a cache before returning it.
Expand All @@ -115,7 +116,7 @@ package object memoization {
* @tparam A type of the value to be cached
* @return the result, either retrieved from the cache or calculated by executing the function `f`
*/
def memoizeSync[A](ttl: Duration)(f: => A)(implicit scalaCache: ScalaCache, flags: Flags): A = macro Macros.memoizeSyncImplWithTTL[A]
def memoizeSync[A](ttl: Duration)(f: => A)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[A]): A = macro Macros.memoizeSyncImplWithTTL[A]

/**
* Perform the given operation and memoize its result to a cache before returning it.
Expand All @@ -135,6 +136,6 @@ package object memoization {
* @tparam A type of the value to be cached
* @return the result, either retrieved from the cache or calculated by executing the function `f`
*/
def memoizeSync[A](optionalTtl: Option[Duration])(f: => A)(implicit scalaCache: ScalaCache, flags: Flags): A = macro Macros.memoizeSyncImplWithOptionalTTL[A]
def memoizeSync[A](optionalTtl: Option[Duration])(f: => A)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[A]): A = macro Macros.memoizeSyncImplWithOptionalTTL[A]
}

26 changes: 14 additions & 12 deletions core/src/main/scala/scalacache/package.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

import com.typesafe.scalalogging.StrictLogging

import scala.concurrent.duration.Duration
import scala.concurrent.{ ExecutionContext, Await, Future }
import scala.util.Try
import scalacache.serdes.Codec

package object scalacache extends StrictLogging {

class TypedApi[V](implicit val scalaCache: ScalaCache) {
class TypedApi[V](implicit val scalaCache: ScalaCache, codec: Codec[V]) {

def get(keyParts: Any*)(implicit flags: Flags): Future[Option[V]] = getWithKey(toKey(keyParts))

Expand Down Expand Up @@ -126,7 +128,7 @@ package object scalacache extends StrictLogging {
*
* @tparam V the type of values that the cache will accept
*/
def typed[V](implicit scalaCache: ScalaCache) = new TypedApi[V]()(scalaCache)
def typed[V: Codec](implicit scalaCache: ScalaCache) = new TypedApi[V]()(scalaCache, implicitly[Codec[V]])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a parameter to the implicit param list instead of using implicitly.

(See slide 84 of http://plastic-idolatry.com/erik/sw2015.pdf)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. didn't know there was a cost to implicitly. Fixed.


/**
* Get the value corresponding to the given key from the cache.
Expand All @@ -137,7 +139,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the corresponding value
* @return the value, if there is one
*/
def get[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Future[Option[V]] =
def get[V: Codec](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Future[Option[V]] =
typed[V].get(keyParts: _*)

/**
Expand All @@ -150,7 +152,7 @@ package object scalacache extends StrictLogging {
* @return the value, if there is one
*/
@deprecated("This method has moved. Please use scalacache.sync.get", "0.7.0")
def getSync[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Option[V] =
def getSync[V: Codec](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Option[V] =
sync.get[V](keyParts: _*)

/**
Expand All @@ -163,7 +165,7 @@ package object scalacache extends StrictLogging {
* @param ttl Time To Live (optional, if not specified then the entry will be cached indefinitely)
* @tparam V the type of the corresponding value
*/
def put[V](keyParts: Any*)(value: V, ttl: Option[Duration] = None)(implicit scalaCache: ScalaCache, flags: Flags): Future[Unit] =
def put[V: Codec](keyParts: Any*)(value: V, ttl: Option[Duration] = None)(implicit scalaCache: ScalaCache, flags: Flags): Future[Unit] =
typed[V].put(keyParts: _*)(value, ttl)

/**
Expand Down Expand Up @@ -201,7 +203,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def caching[V](keyParts: Any*)(f: => Future[V])(implicit scalaCache: ScalaCache, flags: Flags, execContext: ExecutionContext = ExecutionContext.global): Future[V] =
def caching[V](keyParts: Any*)(f: => Future[V])(implicit scalaCache: ScalaCache, flags: Flags, execContext: ExecutionContext = ExecutionContext.global, codec: Codec[V]): Future[V] =
typed[V].caching(keyParts: _*)(f)

/**
Expand All @@ -223,10 +225,10 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def cachingWithTTL[V](keyParts: Any*)(ttl: Duration)(f: => Future[V])(implicit scalaCache: ScalaCache, flags: Flags, execContext: ExecutionContext = ExecutionContext.global): Future[V] =
def cachingWithTTL[V](keyParts: Any*)(ttl: Duration)(f: => Future[V])(implicit scalaCache: ScalaCache, flags: Flags, execContext: ExecutionContext = ExecutionContext.global, codec: Codec[V]): Future[V] =
typed[V].cachingWithTTL(keyParts: _*)(ttl)(f)

def cachingWithOptionalTTL[V](keyParts: Any*)(optionalTtl: Option[Duration])(f: => Future[V])(implicit scalaCache: ScalaCache, flags: Flags, execContext: ExecutionContext = ExecutionContext.global): Future[V] =
def cachingWithOptionalTTL[V](keyParts: Any*)(optionalTtl: Option[Duration])(f: => Future[V])(implicit scalaCache: ScalaCache, flags: Flags, execContext: ExecutionContext = ExecutionContext.global, codec: Codec[V]): Future[V] =
typed[V].cachingWithOptionalTTL(keyParts: _*)(optionalTtl)(f)

private def toKey(parts: Seq[Any])(implicit scalaCache: ScalaCache): String =
Expand All @@ -246,7 +248,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the corresponding value
* @return the value, if there is one
*/
def get[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Option[V] =
def get[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[V]): Option[V] =
typed[V].sync.get(keyParts: _*)

/**
Expand All @@ -263,7 +265,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def caching[V](keyParts: Any*)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V =
def caching[V](keyParts: Any*)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[V]): V =
typed[V].sync.caching(keyParts: _*)(f)

/**
Expand All @@ -279,7 +281,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def cachingWithTTL[V](keyParts: Any*)(ttl: Duration)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V =
def cachingWithTTL[V](keyParts: Any*)(ttl: Duration)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[V]): V =
typed[V].sync.cachingWithTTL(keyParts: _*)(ttl)(f)

/**
Expand All @@ -295,7 +297,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def cachingWithOptionalTTL[V](keyParts: Any*)(optionalTtl: Option[Duration])(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V = {
def cachingWithOptionalTTL[V](keyParts: Any*)(optionalTtl: Option[Duration])(f: => V)(implicit scalaCache: ScalaCache, flags: Flags, codec: Codec[V]): V = {
optionalTtl match {
case Some(ttl) => typed[V].sync.cachingWithTTL(keyParts: _*)(ttl)(f)
case None => typed[V].sync.caching(keyParts: _*)(f)
Expand Down
Loading