2

Say I have the following functions:

case class ErrA(msg: String)

case class ErrB(msg: String)

def doA(): Either[ErrA, Int] = Right(2)

def doB(): Either[ErrB, Int] = Right(3)

ErrA and ErrB are unrelated types and are not actually declared in the same file outside of this example. They cannot be easily made to inherit from a common type.

I would like to introduce a new type that would represent both error types:

sealed trait ErrAOrB

case class ErrAWrapper(e: ErrA) extends ErrAOrB

case class ErrBWrapper(e: ErrB) extends ErrAOrB

And then write the following function using a for comprehension:

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right;
       b <- doB().right) yield a + b

Is there any way to get the compiler to implicitly convert those specific Either types to the common Either[ErrAOrB, Int] type?

For instance:

implicit def wrapA[T](res: Either[ErrA, T]): Either[ErrAOrB, T] = res.left.map(ErrAWrapper(_))

implicit def wrapB[T](res: Either[ErrB, T]): Either[ErrAOrB, T] = res.left.map(ErrBWrapper(_))

But that doesn't work because the implicit conversion gets applied only to the final expression in the for comprehension, and then the compiler has to bind it to doA and since types ErrA and ErrAOrB are unrelated the best it can do is to make the generics make sense is to use Object which is not compatible with the expected type.

Ivan Poliakov
  • 2,245
  • 1
  • 18
  • 19
  • 1
    just to note that implicit views are not recommended in Scala - use implicit class instead, so it could be smthng like `doA().right.wrap`. Just imagine people reading your code and wondering about how did `Either[ErrA, Int]` became `ErrAOrB`? No IDE hint, so no good way to find your `wrapA` implicits – dk14 Nov 10 '16 at 11:41

1 Answers1

3

Implicit views are not recommended in Scala, as you can see from compiler's feature warning when you define wrapA/wrapB.

Even if you take your chances by defining implicit view over Either.RightProjection instead Either - Just imagine people reading your code and wondering about how did Either[ErrA, Int] become Either[ErrAOrB, Int]? No IDE hint, so no good way to find your wrapA implicits

So, use implicit class instead:

implicit class WrapA[T](x: Either[ErrA, T]) {
  def wrap = x.left.map(ErrAWrapper(_)): Either[ErrAOrB, T]
}

implicit class WrapB[T](x: Either[ErrB, T]) {
  def wrap = x.left.map(ErrBWrapper(_)): Either[ErrAOrB, T]
}

scala> def doAplusB(): Either[ErrAOrB, Int] =
     |   for {
     |      a <- doA().wrap.right
     |      b <- doB().wrap.right
     |   } yield a + b
doAplusB: ()Either[ErrAOrB,Int]

P.S. You don't need monad for + operation if your computations are independent (like in your example) - Applicative is enough. Take a look at cats.data.Validated or scalaz.Validation for instance.


Answering wether it's possible or not to trick scalac:

implicit def wrapBB[T](res: Either.RightProjection[ErrB, T]): Either.RightProjection[ErrAOrB, T] = res.e.left.map(ErrBWrapper(_)).right

implicit def wrapAA[T](res: Either.RightProjection[ErrA, T]): Either.RightProjection[ErrAOrB, T] = res.e.left.map(ErrAWrapper(_)).right

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right: Either.RightProjection[ErrAOrB, Int] ;
       b <- doB().right: Either.RightProjection[ErrAOrB, Int]) yield a + b

But this requires Either.RightProjection type ascription, but if you had some parallel assignment (Applicative-style instead of for-comprehension), I believe the thing with ad-hoc supertype could work.

Or even (with your wrapB defined):

implicit def wrapAA[T] ...
implicit def wrapBB[T] ...

implicit def wrapB[T](res: Either[ErrB, T]): Either[ErrAOrB, T] = res.left.map(ErrBWrapper(_))

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right:  Either.RightProjection[ErrAOrB, Int];
       b <- doB().right) yield a + b

The reason is an expansion to:

doA().right.flatMap(a => doB().right.map(b => a + b))

flatMap requires RightProjection to be returned, but map doesn't.

dk14
  • 22,206
  • 4
  • 51
  • 88
  • 1
    I completely agree that explicit is better than implicit, I was just curious if it was possible to abuse the implicits to achieve this without any explicit conversions at all. The actual operations are dependent on each other. – Ivan Poliakov Nov 10 '16 at 12:08