fetch icon indicating copy to clipboard operation
fetch copied to clipboard

It executes liftF effect twice in a product composition

Open TalkingFoxMid opened this issue 4 months ago • 0 comments

I've found that this piece of code prints "message" twice:

object Main extends App {
  object StringByString extends Data[String, String] {
    type StringByString[F[_]] = DataSource[F, String, String]
    def name: String = "stringByString"

    def source[F[_]: Concurrent]: StringByString[F] = new StringByString[F] {
      override def CF: Concurrent[F] = Concurrent[F]

      override def data: Data[String, String] = StringByString

      override def fetch(accountId: String): F[Option[String]] =
        Option(accountId).pure[F]

    }

  }
  val ds = StringByString.source[IO]
  
  val f = Fetch.optional(
    "string", ds
  ).product(Fetch.liftF(IO(println("message"))))

  println(Fetch.run(f).unsafeRunSync())

}

When a BlockedRequest is followed by Fetch.liftF, interpreter will execute that effect twice.

It is not very pleasant for us because in that Fetch.liftF we do a bunch of computational efforts and duplicating these computations might be quite wasteful.

Possible solution:

The problem I guess is in that piece of code:

override def product[A, B](fa: Fetch[F, A], fb: Fetch[F, B]): Fetch[F, (A, B)] =
        Unfetch[F, (A, B)](for {
          fab <- (fa.run, fb.run).tupled
          result = fab match {
            case (Throw(e), _) =>
              Throw[F, (A, B)](e)
            case (Done(a), Done(b)) =>
              Done[F, (A, B)]((a, b))
            case (Done(a), Blocked(br, c)) =>
              Blocked[F, (A, B)](br, product(fa, c))
            case (Blocked(br, c), Done(b)) =>
              Blocked[F, (A, B)](br, product(c, fb))
            case (Blocked(br, c), Blocked(br2, c2)) =>
              Blocked[F, (A, B)](combineRequestMaps(br, br2), product(c, c2))
            case (_, Throw(e)) =>
              Throw[F, (A, B)](e)
          }
        } yield result)

Here we execute fb (which is Fetch.liftF) twice:

  1. (fa.run, fb.run).tupled
  2. product(c, fb)

We could reuse the result of fb by replacing product(c, fb) to c.map((_, b))

TalkingFoxMid avatar Sep 15 '25 06:09 TalkingFoxMid