Преобразование F[A] в будущее [A]
У меня есть хранилище:
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()
}
Мне не нравится, что этот подход требует неявных преобразователей для каждого типа, используемого в хранилище. Есть лучший способ сделать это?
1 ответ
Это именно то, для чего нужен конечный подход без тегов, чтобы абстрагироваться от 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]
может быть достаточно сильным для этого.