Let's start with a simple problem. Let's say we need to get the sum of two integers wrapped in Future and Option . Suppose we use the cats library.
If we use the monad (aka flatMap ) method, we need:
- both
Future and Option must have Monad instances defined above them - we also need the
OptionT monadic transformer, which will only work for Option (exactly F[Option[T]] )
So, here is the code (let them forget about understanding and raising, to make it easier):
val fa = OptionT[Future, Int](Future(Some(1))) val fb = OptionT[Future, Int](Future(Some(2))) fa.flatMap(a => fb.map(b => a + b)) //note that a and b are already Int not Future's
if you look at the sources of OptionT.flatMap :
def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] = flatMapF(a => f(a).value) def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] = OptionT(F.flatMap(value)(_.fold(F.pure[Option[B]](None))(f)))
You will notice that the code is quite specific for the internal logic and structure of Option ( fold , None ). Same issue for EitherT , StateT , etc.
It is important that cats do not have FutureT , so you can make Future[Option[T]] , but you cannot do it with Option[Future[T]] (I will show later that this problem is even more general).
On the other hand, if you select a composition using Applicative , you only need to fulfill one requirement:
- both
Future and Option must have Applicative instances defined above them
You do not need special transformers for Option , basically the cat library provides the Nested class, which works for any Applicative (let's forget about using carrier sugar to make it easier to understand):
val fa = Nested[Future, Option, Int](Future(Some(1))) val fb = Nested[Future, Option, Int](Future(Some(1))) fa.map(x => (y: Int) => y + x).ap(fb)
Change them:
val fa = Nested[Option, Future, Int](Some(Future(1))) val fb = Nested[Option, Future, Int](Some(Future(1))) fa.map(x => (y: Int) => y + x).ap(fb)
Work!
So yes, Monad Applicative, Option[Future[T]] is still a monad (on Future[T] , but not on T ), but it only allows you to work with Future[T] not T To "combine" Option with Future layers - you need to define a FutureT monadic transformer; to combine Future with Option - you need to define OptionT . And OptionT defined in cat / scalaz, but not FutureT .
In general (from here ):
Unfortunately, our real goal, the composition of the monads, is rather complicated. In fact, we can actually prove that in a certain sense, it is not possible to construct a union function with the type indicated above using only the operations of two monads (see the appendix for the proof scheme). It follows that the only way we could hope to form a composition is if there are some additional constructs connecting the two components
And this composition is not even commutative, as I showed for Option and Future .
As an exercise, you can try defining FutureT flatMap:
def flatMapF[B](f: A => F[Future[B]])(implicit F: Monad[F]): FutureT[F, B] = FutureT(F.flatMap(value){ x: Future[A] => val r: Future[F[Future[B]] = x.map(f) //you have to return F[Future[B]] here using only f and F.pure, //where F can be List, Option whatever })
basically the problem with this implementation is that you need to “extract” the value from r, which is not possible here if you cannot extract the value from Future (comonad is not defined on it), which is true if we are talking about “non-blocking” API This basically means that you cannot "swap" Future and F , for example Future[F[Future[B]] => F[Future[Future[B] , which, by the way, is a natural transformation (morphism between functors). So this explains the first comment of this general answer :
you can create monads if you can provide a natural exchange of transformation: NM a → MN a
Applicative however, does not have such problems - you can easily compose them, but keep in mind that the result of a composition of two Applicatives cannot be a monad (but will always be applicative). Nested[Future, Option, T] not a monad on T , regardless of the fact that both Option and Future are monads on T Entering simple words Nested in a class does not have flatMap .
It would also be useful to read:
Putting it all together ( F and G are monads)
F[G[T]] is a monad on G[T] , but not on TG_TRANSFORMER[F, T] is required to get the monad on T from F[G[T]] .- there is no
MEGA_TRANSFORMER[G, F, T] , since such a transformer cannot be built on top of the monad - this requires additional operations defined on G (it seems that comonad on G should be enough) - each monad (including
G and F ) is applicative, but not every applicative is a monad - Theoretically,
F[G[T]] is applicative for both G[T] and T However, scala requires the creation of NESTED[F, G, T] in order to get a composite application on T (which is implemented in the cat library). NESTED[F, G, T] is an applicative but not a monad
This means that you can compose Future x Option (aka Option[Future[T]] ) in one monad, but you cannot compose Option x Future (aka Future[Option[T]] ) without knowing what they are than other than monads. And you can compose any two applications in one application, you can also compose any two monads for one single applicative (but not in one monad).