使用 Scala 和 Cats 使实现更加通用

问题描述 投票:0回答:1

我使用 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))
}
scala scala-cats
1个回答
0
投票

首先,正确的答案不是重新发明轮子,而只是使用已经完成所有这一切的库,例如mules

但是,为了学习,让我们看一下您可以改进的一些事情。

  1. 界面。特别是
    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]
  1. 创造。由于
    Cache
    是一个可变状态,因此它的创建也必须是一个效果;喜欢你原来的例子,但与你的尝试不同
    Ref
def empty[F[_], K, V]: F[Cache[F, K, V]] 
  1. 实施。你的直觉是对的,我们想使用可变引用来引用不可变的
    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))
    }
  }
  1. 约束,上面的代码需要对
    F
    进行约束才能实际工作。但实际上,我们不需要所有
    Async
    的力量,而只需要
    Concurrent
    ,因为我们需要的只是创建一个
    Ref
    :)
def empty[F[_] : Concurrent, K, V]: F[Cache[F, K, V]] = ...
© www.soinside.com 2019 - 2024. All rights reserved.