我有一个存储库:
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()
}
我不喜欢这种方法需要为存储库中使用的每种类型使用隐式转换器。有更好的方法吗?
答案 0 :(得分:4)
这正是无标记最终方法的用途,通过要求它遵循某些特定类型约束来抽象F
。例如,让我们创建一个自定义实现,该实现需要F
为Applicative
:
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
上。如果我们需要顺序计算,我们可以使用Monad[F]
然后使用flatMap
结果。如果不需要顺序性,Applicative[F]
可能足够强大。