Why monads do not compose in scala

Why monads do not compose, when the Monad is applicative, and the Applicator is a Functor. You see this chain of inheritance in many articles on the Internet (what I experienced). But when the Functors and Applicators compose, why do Monads violate this?

Can someone provide a simple example in scala that demonstrates this problem? I know that this is given a lot, but it is difficult to understand without a simple example.

+6
source share
2 answers

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 T
  • G_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).

+9
source

Tony Morris spoke about monad transformers, which very well explain this exact problem.

http://tonymorris.imtqy.com/blog/posts/monad-transformers/

It uses haskell, but the examples are easily translatable into scala.

0
source

All Articles