Skip to content

Commit

Permalink
Safer Either casting
Browse files Browse the repository at this point in the history
  • Loading branch information
adelbertc committed Aug 17, 2016
1 parent d4df094 commit eedd057
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 21 deletions.
3 changes: 2 additions & 1 deletion core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package data

import cats.functor.Bifunctor
import cats.instances.either._
import cats.syntax.EitherUtil
import cats.syntax.either._

/**
Expand Down Expand Up @@ -72,7 +73,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {

def flatMap[D](f: B => EitherT[F, A, D])(implicit F: Monad[F]): EitherT[F, A, D] =
EitherT(F.flatMap(value) {
case l @ Left(_) => F.pure((l: Either[A, B]).rightCast[D])
case l @ Left(_) => F.pure(EitherUtil.leftCast(l))
case Right(b) => f(b).value
})

Expand Down
6 changes: 2 additions & 4 deletions core/src/main/scala/cats/instances/either.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats
package instances

import cats.syntax.EitherUtil
import cats.syntax.either._
import scala.annotation.tailrec

Expand Down Expand Up @@ -54,10 +55,7 @@ trait EitherInstances extends EitherInstances1 {

override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] =
fb match {
// This should be safe, but we are forced to use `asInstanceOf`,
// because `Left[+A, +B]` extends Either[A, B] instead of
// `Either[A, Nothing]`
case l @ Left(_) => Now(l.asInstanceOf[Either[A, Z]])
case l @ Left(_) => Now(EitherUtil.leftCast(l))
case Right(b) => fc.map(_.right.map(f(b, _)))
}

Expand Down
36 changes: 20 additions & 16 deletions core/src/main/scala/cats/syntax/either.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal {
}

def orElse[C](fallback: => Either[C, B]): Either[C, B] = eab match {
case Left(_) => fallback
case Right(_) => leftCast[C]
case Left(_) => fallback
case r @ Right(_) => EitherUtil.rightCast(r)
}

def recover(pf: PartialFunction[A, B]): Either[A, B] = eab match {
Expand Down Expand Up @@ -103,24 +103,24 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal {
}

def map[C](f: B => C): Either[A, C] = eab match {
case Left(_) => rightCast[C]
case Right(b) => Right(f(b))
case l @ Left(_) => EitherUtil.leftCast(l)
case Right(b) => Right(f(b))
}

def map2Eval[C, Z](fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] =
eab match {
case Left(_) => Now(rightCast[Z])
case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _)))
case l @ Left(_) => Now(EitherUtil.leftCast(l))
case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _)))
}

def leftMap[C](f: A => C): Either[C, B] = eab match {
case Left(a) => Left(f(a))
case Right(_) => leftCast[C]
case Left(a) => Left(f(a))
case r @ Right(_) => EitherUtil.rightCast(r)
}

def flatMap[D](f: B => Either[A, D]): Either[A, D] = eab match {
case Left(_) => rightCast[D]
case Right(b) => f(b)
case l @ Left(_) => EitherUtil.leftCast(l)
case Right(b) => f(b)
}

def compare(that: Either[A, B])(implicit A: Order[A], B: Order[B]): Int = eab match {
Expand Down Expand Up @@ -163,8 +163,8 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal {
}

def traverse[F[_], D](f: B => F[D])(implicit F: Applicative[F]): F[Either[A, D]] = eab match {
case Left(_) => F.pure(rightCast[D])
case Right(b) => F.map(f(b))(Right(_))
case l @ Left(_) => F.pure(EitherUtil.leftCast(l))
case Right(b) => F.map(f(b))(Right(_))
}

def foldLeft[C](c: C)(f: (C, B) => C): C = eab match {
Expand Down Expand Up @@ -254,10 +254,6 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal {
* }}}
*/
def toEitherT[F[_]: Applicative]: EitherT[F, A, B] = EitherT.fromEither(eab)

private[cats] def leftCast[C]: Either[C, B] = eab.asInstanceOf[Either[C, B]]

private[cats] def rightCast[C]: Either[A, C] = eab.asInstanceOf[Either[A, C]]
}

final class EitherObjectOps(val either: Either.type) extends AnyVal { // scalastyle:off ensure.single.space.after.token
Expand Down Expand Up @@ -314,3 +310,11 @@ final class CatchOnlyPartiallyApplied[T] private[syntax] {
Left(t.asInstanceOf[T])
}
}

private[cats] object EitherUtil {
/** Cast the *right* type parameter of a `Left`. */
def leftCast[A, B, C](l: Left[A, B]): Either[A, C] = l.asInstanceOf[Left[A, C]]

/** Cast the *left* type parameter of a `Right` */
def rightCast[A, B, C](r: Right[A, B]): Either[C, B] = r.asInstanceOf[Right[C, B]]
}

0 comments on commit eedd057

Please sign in to comment.