From 66b92438973ed6ad6633d10690f07ae27607ce1c Mon Sep 17 00:00:00 2001 From: Andrey Bobylev Date: Fri, 7 Feb 2020 13:54:58 +0300 Subject: [PATCH 1/2] Add getOrElse method --- README.md | 4 ++++ .../com/evolutiongaming/scache/Cache.scala | 6 +++++ .../evolutiongaming/scache/CacheFenced.scala | 2 ++ .../evolutiongaming/scache/CacheMetered.scala | 2 ++ .../scache/ExpiringCache.scala | 2 ++ .../evolutiongaming/scache/LoadingCache.scala | 2 ++ .../scache/PartitionedCache.scala | 2 ++ .../evolutiongaming/scache/SerialMap.scala | 7 ++++++ .../scache/CacheEmptySpec.scala | 11 +++++++++ .../scache/CacheFencedTest.scala | 9 +++++++ .../evolutiongaming/scache/CacheSpec.scala | 24 +++++++++++++++++++ .../scache/SerialMapSpec.scala | 17 +++++++++++++ 12 files changed, 88 insertions(+) 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 { From f9a65fdb228188c7468d25d3c181c6999ffed3e1 Mon Sep 17 00:00:00 2001 From: Andrey Bobylev Date: Fri, 7 Feb 2020 15:18:00 +0300 Subject: [PATCH 2/2] change to effectful value --- README.md | 4 ++-- src/main/scala/com/evolutiongaming/scache/Cache.scala | 6 +++--- src/main/scala/com/evolutiongaming/scache/CacheFenced.scala | 6 +++--- .../scala/com/evolutiongaming/scache/CacheMetered.scala | 2 +- .../scala/com/evolutiongaming/scache/ExpiringCache.scala | 2 +- .../scala/com/evolutiongaming/scache/LoadingCache.scala | 2 +- .../scala/com/evolutiongaming/scache/PartitionedCache.scala | 2 +- src/main/scala/com/evolutiongaming/scache/SerialMap.scala | 6 +++--- .../scala/com/evolutiongaming/scache/CacheEmptySpec.scala | 4 ++-- .../scala/com/evolutiongaming/scache/CacheFencedTest.scala | 2 +- src/test/scala/com/evolutiongaming/scache/CacheSpec.scala | 6 +++--- .../scala/com/evolutiongaming/scache/SerialMapSpec.scala | 4 ++-- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ced5275..37a388f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ trait Cache[F[_], K, V] { def get(key: K): F[Option[V]] - def getOrElse(key: K, default: => V): F[V] + def getOrElse(key: K, default: => F[V]): F[V] /** * Does not run `value` concurrently for the same key @@ -72,7 +72,7 @@ trait SerialMap[F[_], K, V] { def get(key: K): F[Option[V]] - def getOrElse(key: K, default: => V): F[V] + def getOrElse(key: K, default: => F[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 a5f7bd6..29f38b8 100644 --- a/src/main/scala/com/evolutiongaming/scache/Cache.scala +++ b/src/main/scala/com/evolutiongaming/scache/Cache.scala @@ -13,7 +13,7 @@ trait Cache[F[_], K, V] { def get(key: K): F[Option[V]] - def getOrElse(key: K, default: => V): F[V] + def getOrElse(key: K, default: => F[V]): F[V] /** * Does not run `value` concurrently for the same key @@ -63,7 +63,7 @@ object Cache { def get(key: K) = none[V].pure[F] - def getOrElse(key: K, default: => V): F[V] = default.pure[F] + def getOrElse(key: K, default: => F[V]): F[V] = default def getOrUpdate(key: K)(value: => F[V]) = value @@ -137,7 +137,7 @@ object Cache { def get(key: K) = fg(self.get(key)) - def getOrElse(key: K, default: => V): G[V] = fg(self.getOrElse(key, default)) + def getOrElse(key: K, default: => G[V]): G[V] = fg(self.getOrElse(key, gf(default))) def getOrUpdate(key: K)(value: => G[V]) = fg(self.getOrUpdate(key)(gf(value))) diff --git a/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala b/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala index f254d52..391cb08 100644 --- a/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala +++ b/src/main/scala/com/evolutiongaming/scache/CacheFenced.scala @@ -1,6 +1,6 @@ package com.evolutiongaming.scache -import cats.FlatMap +import cats.Monad import cats.effect.concurrent.Ref import cats.effect.{Concurrent, Resource} import cats.implicits._ @@ -29,13 +29,13 @@ object CacheFenced { result.fenced } - def apply[F[_] : FlatMap, K, V](cache: Cache[F, K, V], fence: F[Unit]): Cache[F, K, V] = { + def apply[F[_] : Monad, K, V](cache: Cache[F, K, V], fence: F[Unit]): Cache[F, K, V] = { new Cache[F, K, V] { def get(key: K) = cache.get(key) - def getOrElse(key: K, default: => V): F[V] = cache.getOrElse(key, default) + def getOrElse(key: K, default: => F[V]): F[V] = get(key).flatMap(_.fold(default)(_.pure[F])) def getOrUpdate(key: K)(value: => F[V]) = cache.getOrUpdate(key)(value) diff --git a/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala b/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala index 7448db3..8c03c84 100644 --- a/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala +++ b/src/main/scala/com/evolutiongaming/scache/CacheMetered.scala @@ -44,7 +44,7 @@ object CacheMetered { } yield value } - def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrElse(key: K, default: => F[V]): F[V] = get(key).flatMap(_.fold(default)(_.pure[F])) def getOrUpdate(key: K)(value: => F[V]) = { getOrUpdateReleasable(key) { diff --git a/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala b/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala index c01b684..1004cab 100644 --- a/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala +++ b/src/main/scala/com/evolutiongaming/scache/ExpiringCache.scala @@ -179,7 +179,7 @@ object ExpiringCache { } } - def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrElse(key: K, default: => F[V]): F[V] = get(key).flatMap(_.fold(default)(_.pure[F])) def getOrUpdate(key: K)(value: => F[V]) = { diff --git a/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala b/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala index 34b84e8..e25f0a0 100644 --- a/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala +++ b/src/main/scala/com/evolutiongaming/scache/LoadingCache.scala @@ -104,7 +104,7 @@ object LoadingCache { .handleError { _ => none[V] } } - def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrElse(key: K, default: => F[V]): F[V] = get(key).flatMap(_.fold(default)(_.pure[F])) def getOrUpdate(key: K)(value: => F[V]) = { getOrUpdateReleasable1(key) { diff --git a/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala b/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala index d8f60d2..af7bd55 100644 --- a/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala +++ b/src/main/scala/com/evolutiongaming/scache/PartitionedCache.scala @@ -18,7 +18,7 @@ object PartitionedCache { cache.get(key) } - def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrElse(key: K, default: => F[V]): F[V] = get(key).flatMap(_.fold(default)(_.pure[F])) def getOrUpdate(key: K)(value: => F[V]) = { val cache = partitions.get(key) diff --git a/src/main/scala/com/evolutiongaming/scache/SerialMap.scala b/src/main/scala/com/evolutiongaming/scache/SerialMap.scala index ddb3a03..82a3662 100644 --- a/src/main/scala/com/evolutiongaming/scache/SerialMap.scala +++ b/src/main/scala/com/evolutiongaming/scache/SerialMap.scala @@ -15,7 +15,7 @@ trait SerialMap[F[_], K, V] { def get(key: K): F[Option[V]] - def getOrElse(key: K, default: => V): F[V] + def getOrElse(key: K, default: => F[V]): F[V] def put(key: K, value: V): F[Option[V]] @@ -49,7 +49,7 @@ object SerialMap { self => def get(key: K) = none[V].pure[F] - def getOrElse(key: K, default: => V): F[V] = default.pure[F] + def getOrElse(key: K, default: => F[V]): F[V] = default def put(key: K, value: V) = none[V].pure[F] @@ -97,7 +97,7 @@ object SerialMap { self => } - def getOrElse(key: K, default: => V): F[V] = get(key).map(_.getOrElse(default)) + def getOrElse(key: K, default: => F[V]): F[V] = get(key).flatMap(_.fold(default)(_.pure[F])) def put(key: K, value: V) = { diff --git a/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala b/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala index 4426019..453529a 100644 --- a/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala +++ b/src/test/scala/com/evolutiongaming/scache/CacheEmptySpec.scala @@ -24,10 +24,10 @@ class CacheEmptySpec extends AsyncFunSuite with Matchers { test("getOrElse") { val result = for { - value <- cache.getOrElse(0, 1) + value <- cache.getOrElse(0, 1.pure[IO]) _ <- Sync[IO].delay { value shouldEqual 1 } _ <- cache.put(0, 2) - value <- cache.getOrElse(0, 1) + value <- cache.getOrElse(0, 1.pure[IO]) _ <- Sync[IO].delay { value shouldEqual 1 } } yield {} result.run() diff --git a/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala b/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala index 83aa0ea..fc3b9a6 100644 --- a/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala +++ b/src/test/scala/com/evolutiongaming/scache/CacheFencedTest.scala @@ -25,7 +25,7 @@ class CacheFencedTest extends AsyncFunSuite with Matchers { test("getOrElse succeeds after cache is released") { val result = for { cache <- cache.use(_.pure[IO]) - a <- cache.getOrElse(0, 1) + a <- cache.getOrElse(0, 1.pure[IO]) _ = a shouldEqual 1 } yield {} result.run() diff --git a/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala b/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala index 3b69833..27db7fc 100644 --- a/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala +++ b/src/test/scala/com/evolutiongaming/scache/CacheSpec.scala @@ -62,10 +62,10 @@ class CacheSpec extends AsyncFunSuite with Matchers { test(s"getOrElse: $name") { val result = cache.use { cache => for { - value <- cache.getOrElse(0, 1) + value <- cache.getOrElse(0, 1.pure[IO]) _ = value shouldEqual 1 _ <- cache.put(0, 2) - value <- cache.getOrElse(0, 1) + value <- cache.getOrElse(0, 1.pure[IO]) _ = value shouldEqual 2 } yield {} } @@ -76,7 +76,7 @@ class CacheSpec extends AsyncFunSuite with Matchers { test(s"getOrElse succeeds after cache is released: $name") { val result = for { cache <- cache.use(_.pure[IO]) - a <- cache.getOrElse(0, 1) + a <- cache.getOrElse(0, 1.pure[IO]) _ = a shouldEqual 1 } yield {} result.run() diff --git a/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala b/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala index 4dcd6d7..1faf6ab 100644 --- a/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala +++ b/src/test/scala/com/evolutiongaming/scache/SerialMapSpec.scala @@ -83,9 +83,9 @@ class SerialMapSpec extends AsyncFunSuite with Matchers { val key = "key" for { serialMap <- SerialMap.of[F, String, Int] - value0 <- serialMap.getOrElse(key, 1) + value0 <- serialMap.getOrElse(key, 1.pure[F]) _ <- serialMap.put(key, 2) - value1 <- serialMap.getOrElse(key, 1) + value1 <- serialMap.getOrElse(key, 1.pure[F]) } yield { value0 shouldEqual 1 value1 shouldEqual 2