0

I'm trying to implement a query that returns the extracted information inside a fs2.Stream. I defined the Jobs algebra:

trait Jobs[F[_]] {
  def all(): fs2.Stream[F, Job]
}

Then, I implemented an interpreter for the algebra:

final class LiveJobs[F[_]: MonadCancelThrow](postgres: Resource[F, Transactor[F]]) extends Jobs[F] {
  override def all(): fs2.Stream[F, Job] = for {
    jobs <- postgres.use { xa =>
      sql"SELECT * FROM jobs".query[Job].stream.transact(xa)
    }
  } yield jobs
}

However, the compiler yells because the types are not aligned:

type mismatch;
[error]  found   : fs2.Stream[[_]F[_],Job]
[error]  required: F[?]
[error]       sql"SELECT * FROM jobs".query[Job].stream.transact(xa)
[error]                                                         ^
[error] one error found

The Resource.use method needs a function that produces an F[*], not an fs2.Stream[F, Job]. I cannot find anything that lets me convert between the two types or a different way to use the postgres resource.

riccardo.cardin
  • 7,971
  • 5
  • 57
  • 106
  • 1
    You want `Stream.resource(postgres).flatMap(xa => sql"SELECT * FROM jobs".query[Job].stream.transact(xa))` - also, you probably do not want to adquiere the transactor for each query, rather, the constructor of that class should receive the transactor directly and the factory of the class should return a `Resource` of itself. – Luis Miguel Mejía Suárez Aug 22 '22 at 20:07
  • Many thanks, Luis. So, the transaction will be acquired every time I call `use`? Hmm, terrible idea. Thanks a lot. If you want to answer the question, I'll accept it :) – riccardo.cardin Aug 23 '22 at 05:01

1 Answers1

1

The following is probably the design you want to follow:

trait Jobs[F[_]] {
  def all: fs2.Stream[F, Job] =
}

object Jobs {
  // I am not exactly sure which typeclass you require here, so i will use Async
  def live[F[_]](implicit ev: Async[F]): Resource[F, Jobs[F]] = {
    val transactor: Resource[F, Transactor[F]] = ... // Whatever you already have here.
    transactor.map(xa => new LiveJobs(xa))
  }
}

private[pckg] final class LiveJobs[F[_]](xa: Transactor[F])(implicit ev: MonadCancelThrow[F]) extends Jobs[F] {
  override final val all: fs2.Stream[F, Job] =
    sql"SELECT * FROM jobs".query[Job].stream.transact(xa)
}

Also, my personal advice, stick to concrete IO while learning; and maybe even after. The whole F[_] thing will just cause more trouble than worth originally.

  • Thanks, Luis. Only one more question: Why should the factory return a `Resource[F, Jobs[F]]`? I mean, `Jobs` algebra (and its interpreter) is not a resource. – riccardo.cardin Aug 23 '22 at 14:09
  • @riccardo.cardin because in order to create a `LiveJobs` instance you need a `Transactor` which is a resource that has to be acquired and released, thus your implementation life-cycle is tied to that one, if you want to use the instance you need the transactor open and if you close / free the instance you want to close the transactor, does that makes sense? – Luis Miguel Mejía Suárez Aug 23 '22 at 14:13
  • > if you close / free the instance you want to close the transaction So, if the `Transactor` is shared among more than one algebra, I can avoid the factory returning a `Resource`. Is it right? – riccardo.cardin Aug 23 '22 at 14:21
  • 1
    @riccardo.cardin if the `Transactor` is required in many then yeah, the factory method `live` will receive a `(xa: Transactor[F])` and return a `Jobs[F]` directly _(and probably would only require `MonadCancelThrow[F]` rather than `Async[F]`)_ – Luis Miguel Mejía Suárez Aug 23 '22 at 14:24
  • Many thanks. I really appreciate your patience :) – riccardo.cardin Aug 23 '22 at 14:31