Преобразование 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] может быть достаточно сильным для этого.

Другие вопросы по тегам