diff --git a/README.md b/README.md index 190a271..ced5275 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ trait Cache[F[_], K, V] { def get(key: K): F[Option[V]] + def getOrElse(key: K, default: => V): F[V] + /** * Does not run `value` concurrently for the same key */ @@ -70,6 +72,8 @@ trait SerialMap[F[_], K, V] { def get(key: K): F[Option[V]] + def getOrElse(key: K, default: => V): F[V] + def put(key: K, value: V): F[Option[V]] /** diff --git a/src/main/scala/com/evolutiongaming/scache/Cache.scala b/src/main/scala/com/evolutiongaming/scache/Cache.scala index 1218558..a5f7bd6 100644 --- a/src/main/scala/com/evolutiongaming/scache/Cache.scala +++ b/src/main/scala/com/evolutiongaming/scache/Cache.scala @@ -13,6 +13,8 @@ trait Cache[F[_], K, V] { def get(key: K): F[Option[V]] + def getOrElse(key: K, default: => V): F[V] + /** * Does not run `value` concurrently for the same key */ @@ -61,6 +63,8 @@ object Cache { def get(key: K) = none[V].pure[F] + def getOrElse(key: K, default: => V): F[V] = default.pure[F] + def getOrUpdate(key: K)(value: => F[V]) = value def getOrUpdateReleasable(key: K)(value: => F[Releasable[F, V]]) = value.map(_.value) @@ -133,6 +137,8 @@ object Cache { def get(key: K) = fg(self.get(key)) + def getOrElse(key: K, default: => V): G[V] = fg(self.getOrElse(key, default)) + def getOrUpdate(key: K)(value: => G[V]) = fg(self.getOrUpdate(key)(gf(value))) def getOrUpdateReleasable(key: K)(value: => G[Releasable[G, V]]) = { diff --git a/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala b/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala index 4c5988e..f254d52 100644 --- a/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala +++ b/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala @@ -35,6 +35,8 @@ object CacheFenced { def get(key: K) = cache.get(key) + def getOrElse(key: K, default: => V): F[V] = cache.getOrElse(key, default) + def getOrUpdate(key: K)(value: => F[V]) = cache.getOrUpdate(key)(value) def getOrUpdateReleasable(key: K)(value: => F[Releasable[F, V]]) = { diff --git a/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala b/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala index c2652f7..7448db3 100644 --- a/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala +++ b/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala @@ -44,6 +44,8 @@ object CacheMetered { } yield value } + def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrUpdate(key: K)(value: => F[V]) = { getOrUpdateReleasable(key) { for { diff --git a/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala b/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala index 8faca52..c01b684 100644 --- a/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala +++ b/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala @@ -179,6 +179,8 @@ object ExpiringCache { } } + def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrUpdate(key: K)(value: => F[V]) = { def entry = { diff --git a/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala b/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala index 605d65d..34b84e8 100644 --- a/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala +++ b/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala @@ -104,6 +104,8 @@ object LoadingCache { .handleError { _ => none[V] } } + def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrUpdate(key: K)(value: => F[V]) = { getOrUpdateReleasable1(key) { value.map { loadedOf(_, none) } diff --git a/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala b/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala index 97b7ebe..d8f60d2 100644 --- a/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala +++ b/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala @@ -18,6 +18,8 @@ object PartitionedCache { cache.get(key) } + def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrUpdate(key: K)(value: => F[V]) = { val cache = partitions.get(key) cache.getOrUpdate(key)(value) diff --git a/src/main/scala/com/evolutiongaming/scache/SerialMap.scala b/src/main/scala/com/evolutiongaming/scache/SerialMap.scala index 9519437..ddb3a03 100644 --- a/src/main/scala/com/evolutiongaming/scache/SerialMap.scala +++ b/src/main/scala/com/evolutiongaming/scache/SerialMap.scala @@ -15,6 +15,8 @@ trait SerialMap[F[_], K, V] { def get(key: K): F[Option[V]] + def getOrElse(key: K, default: => V): F[V] + def put(key: K, value: V): F[Option[V]] /** @@ -47,6 +49,8 @@ object SerialMap { self => def get(key: K) = none[V].pure[F] + def getOrElse(key: K, default: => V): F[V] = default.pure[F] + def put(key: K, value: V) = none[V].pure[F] def modify[A](key: K)(f: Option[V] => F[(Option[V], A)]) = f(none[V]).map { case (_, value) => value } @@ -93,6 +97,9 @@ object SerialMap { self => } + def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + + def put(key: K, value: V) = { modify(key) { prev => (value.some, prev).pure[F] diff --git a/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala b/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala index 14a627e..4426019 100644 --- a/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala +++ b/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala @@ -22,6 +22,17 @@ class CacheEmptySpec extends AsyncFunSuite with Matchers { result.run() } + test("getOrElse") { + val result = for { + value <- cache.getOrElse(0, 1) + _ <- Sync[IO].delay { value shouldEqual 1 } + _ <- cache.put(0, 2) + value <- cache.getOrElse(0, 1) + _ <- Sync[IO].delay { value shouldEqual 1 } + } yield {} + result.run() + } + test("put") { val result = for { value <- cache.put(0, 0) diff --git a/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala b/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala index 9687685..83aa0ea 100644 --- a/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala +++ b/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala @@ -22,6 +22,15 @@ class CacheFencedTest extends AsyncFunSuite with Matchers { result.run() } + test("getOrElse succeeds after cache is released") { + val result = for { + cache <- cache.use(_.pure[IO]) + a <- cache.getOrElse(0, 1) + _ = a shouldEqual 1 + } yield {} + result.run() + } + test(s"put succeeds after cache is released") { val result = for { cache <- cache.use(_.pure[IO]) diff --git a/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala b/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala index 1872720..3b69833 100644 --- a/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala +++ b/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala @@ -59,6 +59,30 @@ class CacheSpec extends AsyncFunSuite with Matchers { } + test(s"getOrElse: $name") { + val result = cache.use { cache => + for { + value <- cache.getOrElse(0, 1) + _ = value shouldEqual 1 + _ <- cache.put(0, 2) + value <- cache.getOrElse(0, 1) + _ = value shouldEqual 2 + } yield {} + } + result.run() + } + + + test(s"getOrElse succeeds after cache is released: $name") { + val result = for { + cache <- cache.use(_.pure[IO]) + a <- cache.getOrElse(0, 1) + _ = a shouldEqual 1 + } yield {} + result.run() + } + + test(s"put: $name") { val result = cache.use { cache => for { diff --git a/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala b/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala index e0602f4..4dcd6d7 100644 --- a/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala +++ b/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala @@ -18,6 +18,10 @@ class SerialMapSpec extends AsyncFunSuite with Matchers { get[IO].run() } + test("getOrElse") { + getOrElse[IO].run() + } + test("put") { put[IO].run() } @@ -75,6 +79,19 @@ class SerialMapSpec extends AsyncFunSuite with Matchers { } } + private def getOrElse[F[_] : Concurrent] = { + val key = "key" + for { + serialMap <- SerialMap.of[F, String, Int] + value0 <- serialMap.getOrElse(key, 1) + _ <- serialMap.put(key, 2) + value1 <- serialMap.getOrElse(key, 1) + } yield { + value0 shouldEqual 1 + value1 shouldEqual 2 + } + } + private def put[F[_] : Concurrent] = { val key = "key" for {