1

我有一个存储库:

trait CustomerRepo[F[_]] {

  def get(id: Identifiable[Customer]): F[Option[CustomerWithId]]
  def get(): F[List[CustomerWithId]]

}

我有一个使用 Cats IO 的数据库实现,所以我有一个CustomerRepoPostgres[IO].

class CustomerRepoPostgres(xa: Transactor[IO]) extends CustomerRepo[IO] {

  import doobie.implicits._

  val makeId = IO {
    Identifiable[Customer](UUID.randomUUID())
  }

  override def get(id: Identifiable[Customer]): IO[Option[CustomerWithId]] =
    sql"select id, name, active from customer where id = $id"
      .query[CustomerWithId].option.transact(xa)

  override def get(): IO[List[CustomerWithId]] =
    sql"select id, name, active from customer"
      .query[CustomerWithId].to[List].transact(xa)

}

现在,我想使用一个不能处理任意持有者类型的库(它只支持Future)。所以我需要一个CustomerRepoPostgres[Future].

我想写一些可以将我转换为的桥接CustomerRepoPostgres[IO]代码CustomerRepoPostgres[Future]

class RepoBridge[F[_]](repo: CustomerRepo[F])
                 (implicit convertList: F[List[CustomerWithId]] => Future[List[CustomerWithId]],
                  convertOption: F[Option[CustomerWithId]] => Future[Option[CustomerWithId]]) {

  def get(id: Identifiable[Customer]): Future[Option[CustomerWithId]] = repo.get(id)
  def get(): Future[List[CustomerWithId]] = repo.get()

}

我不喜欢这种方法对存储库中使用的每种类型都需要隐式转换器。有一个更好的方法吗?

4

1 回答 1

4

这正是无标签最终方法的用途,F通过要求它遵循一些特定的类型约束来抽象。例如,让我们创建一个自定义实现,它需要FApplicative

trait CustomerRepo[F[_]] {
  def get(id: Identifiable[Customer]): F[Option[CustomerWithId]]
  def get(): F[List[CustomerWithId]]
}

class CustorRepoImpl[F[_]](implicit A: Applicative[F]) extends CustomerRepo[F] {
  def get(id: Identifiable[Customer]): F[Option[CustomerWithId]] {
    A.pure(???)
  }

  def get(): F[List[CustomerWithId]] = {
    A.pure(???)
  }
}

这样,无论 的具体类型是F什么,如果它有一个实例,Applicative[F]那么你就可以开始了,无需定义任何转换器。

我们这样做的方式只是F根据我们需要做的处理,把相关的约束放在上面。如果我们需要顺序计算,我们可以使用 a Monad[F],然后flatMap使用结果。如果不需要顺序性,则Applicative[F]可能足够强大。

于 2018-06-02T16:19:20.430 回答