Kevin Meredith
Kevin Meredith

Reputation: 41909

For Comprehension Example to Understand Composing Monads

J. Abrahamson provided an in-depth answer to my composing-monads-v-applicative-functors question.

I gained some intuition, but I do not fully understand his helpful answer.

Given the following Either's:

scala> x
res0: Either[String,Int] = Right(100)

scala> err
res1: Either[Boolean,Int] = Left(false)

I tried to chain them together:

scala> for {
     |   xx <- x.right
     |   yy <- err.right
     | } yield xx + yy
res3: scala.util.Either[Any,Int] = Left(false)

But, of course I don't want an Either[Any, Int]. Yet we get Any since, as I understand, the parent of Either[String, Int] and Either[Boolean, Int] is Either[Any, Int].

When building a for-comprehension, is the typical approach to find an end-type, i.e. Either[String, Int], and then make each flatMap call have that type?

Upvotes: 0

Views: 204

Answers (1)

dk14
dk14

Reputation: 22374

If you mean that you want Either[Boolean, Int] instead of Either[Any, Int], then "You can't always get what you want". Composition of same (as in your example) Either types (but some other values) may return String instead of Boolean even for right projection:

scala> val x: Either[String,Int] = Left("aaa")
x: Either[String,Int] = Left(aaa)

scala> val r: Either[Boolean,Int] = Right(100)
r: Either[Boolean,Int] = Right(100)

scala> for {
     |    xx <- x.right
     |    yy <- r.right //you may swap xx and yy - it doesn't matter for this example, which means that flatMap could be called on any of it         
     | } yield yy + xx
res21: scala.util.Either[Any,Int] = Left(aaa)

So, Either[Any,Int] is really correct and smaller as possible end-type for it.

The desugared version:

scala> x.right.flatMap(xx => r.right.map(_ + xx))
res27: scala.util.Either[Any,Int] = Left(aaa)

Monad's flatMap signature:

flatMap[AA >: A, Y](f: (B) ⇒ Either[AA, Y]): Either[AA, Y]

Passed types:

flatMap[Any >: String, Int](f: (Int) ⇒ Either[?Boolean?, Int]): Either[Any, String]

AA >: A is completely legal here as it allows f = r.right.map(_ + xx) return left type bigger than String (so ?Boolean? becomes Any), otherwise it wouldn't even work. Answering your question, flatMap can't have AA = Boolean here, because A is already at least String, and as shown in first example can actually be a String.

And, by the way, there is no monads composition in this example - r could be just a functor. More than that, you're composing RightProjection with RightProjection here, so they commute automatically.

The only way to inferr Boolean is to kill String type with Nothing - you can do that only if you sure that x is always Right:

scala> val x: Either[Nothing,Int] = Right(100)
x: Either[Nothing,Int] = Right(100)

scala> val r: Either[Boolean,Int] = Left(false)
r: Either[Boolean,Int] = Left(false)

scala> for {
     |    yy <- r.right
     |    xx <- x.right  
     |        
     | } yield yy + xx
res24: scala.util.Either[Boolean,Int] = Left(false)

Then, of course you can't put strings, which is totally correct.

Upvotes: 1

Related Questions