Cartesian builder for OptionT

194 Views Asked by At

I want to join 2 Future[Option[_]]:

def foo: Future[Option[Int]] = ???
def baz: Future[Option[Int]] = ???

It is possible to join pair of Future:

foo |@| baz map( (fooOpt, bazOpt) => ???)

And possible to join pair of Option:

Option(1) |@| Option(2) map ( (a, b) => ???)

How to join pair of OptionT? Doesn't work like that:

OptionT(foo) |@| OptionT(baz) map ( (a, b) => ???)

UPD - here is my imports:

import cats.data.OptionT
import cats.instances.future._
import cats.instances.option._
import cats.syntax.cartesian._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
1

There are 1 best solutions below

0
On BEST ANSWER

EDIT

Turns out, it's a very well known issue, it is caused by SI-2712. If you add the sbt-partial-unification plugin to your project, your original code will work just fine.

As I said, |@| is deprecated, you should change for the following syntax:

import cats.syntax.apply._
(OptionT(foo), OptionT(bar)).mapN(_ + _)

There seems to be an implicit resolution issue. OptionT has a Monad instance, and Monad extends Apply which extends Cartesian so your code should work indeed. It does work if you help the compiler a little bit:

import scala.concurrent.{ExecutionContext, Future}

import cats.data.OptionT
import cats.syntax.cartesian._
import cats.instances.future._

trait CartesianOptionTs  {

  implicit def ec: ExecutionContext

  def foo: Future[Option[Int]]
  def bar: Future[Option[Int]]

  (catsSyntaxCartesian[({type λ[α] = OptionT[Future, α]})#λ, Int](OptionT(foo)) |@| OptionT(bar)).map(_ + _)
}

but if you don't specify the types for catsSyntaxCartesian you get an interesting error:

[error]  found   : cats.data.OptionT[scala.concurrent.Future,Int]
[error]  required: ?F[?A]
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error]  both method ArrowAssoc in object Predef of type [A](self: A)ArrowAssoc[A]
[error]  and method Ensuring in object Predef of type [A](self: A)Ensuring[A]
[error]  are possible conversion functions from cats.data.OptionT[scala.concurrent.Future,Int] to ?F[?A]
[error]       (catsSyntaxCartesian(OptionT(foo)) |@| OptionT(bar)).map(_ + _)

Note that |@| is now deprecated but it looks like you will have the same issue with its replacement mapN.

I can think of two workarounds. If you want to use OptionT, then bring its Apply instance in scope and use map2 directly (you might want to use a type alias instead of a type lambda):

  import cats.Apply
  Apply[({type λ[α] = OptionT[Future, α]})#λ].map2(OptionT(foo), OptionT(bar))(_ + _)

Alternatively, you can drop OptionT altogether and use Apply.compose:

  import cats.instances.option._
  Apply[Future].compose[Option].map2(foo, bar)(_ + _)

The future will be executed in parallel in that case though, so watch out if that's not what you want.