我使用 Scala 中的猫效果在内存中实现了一个简单的缓存。
这是我的特点:
trait Cache[F[_], K, V] {
def get(key: K): F[Option[V]]
def put(key: K, value: V): F[Cache[F, K, V]]
}
import cats.effect.kernel.Async
case class ImmutableMapCache[F[_]: Async, K, V](map: Map[K, V]) extends Cache[F, K, V] {
override def get(key: K): F[Option[V]] =
Async[F].blocking(map.get(key))
override def put(key: K, value: V): F[Cache[F, K, V]] =
Async[F].blocking(ImmutableMapCache(map.updated(key, value)))
}
object ImmutableMapCache {
def empty[F[_]: Async, K, V]: F[Cache[F, K, V]] =
Async[F].pure(ImmutableMapCache(Map.empty))
}
这是一个足够好的实施吗?我将我的效果限制为异步。我可以使其更加通用,以便与 ImmutableMapCache 中的其他效果类型一起使用吗?
我的方法还有哪些其他陷阱?
编辑:
我将 Map 包装在 Cats Ref 上下文中,这是一个更好的实现吗?
import cats.effect.{Ref, Sync}
import cats.syntax.all._
class SimpleCache[F[_]: Sync, K, V] extends Cache[F, K, V] {
private val cache: Ref[F, Map[K, V]] = Ref.unsafe[F, Map[K, V]](Map.empty)
override def put(key: K, value: V): F[Unit] = cache.update(_.updated(key, value))
override def get(key: K): F[Option[V]] = cache.get.map(_.get(key))
}
首先,正确的答案不是重新发明轮子,而只是使用已经完成所有这一切的库,例如mules。
但是,为了学习,让我们看一下您可以改进的一些事情。
put
def put(key: K, value: V): F[Cache[F, K, V]]
如果在
Cache
中放入一个新值会返回一个新值,则意味着它是不可变的,如果它是不可变的则不需要效果,这意味着它只是一个简单的Map
。put
发生突变;但以并发安全的方式。所以它实际上可以用来在不同的Fibers
之间共享数据。因此,put
应该这样定义:
def put(key: K, value: V): F[Unit]
Cache
是一个可变状态,因此它的创建也必须是一个效果;喜欢你原来的例子,但与你的尝试不同 Ref
def empty[F[_], K, V]: F[Cache[F, K, V]]
Map
。现在,为了使其并发安全,我们需要查看对它的访问,或者使用 CAS 循环。 cats-effect 提供了两个选项:分别是 AtomicCell
和 Ref
。在这种情况下,最好只使用 CAS 循环,因此我们使用 Ref
def empty[F[_], K, V]: F[Cache[F, K, V]] =
Ref[F].of(Map.empty[K, V]).map { ref =>
new Cache[F[_], K, V] {
override def get(key: K): F[Option[V]] =
ref.get.map(map => map.get(key))
override def put(key: K, value: V): F[Unit] =
ref.update(map => map.updated(key, value))
}
}
F
进行约束才能实际工作。但实际上,我们不需要所有 Async
的力量,而只需要 Concurrent
,因为我们需要的只是创建一个 Ref
:)def empty[F[_] : Concurrent, K, V]: F[Cache[F, K, V]] = ...