From eedd057830717f87ccf24149a9bc1e15816e2579 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 16 Aug 2016 21:32:04 -0700 Subject: [PATCH] Safer Either casting --- core/src/main/scala/cats/data/EitherT.scala | 3 +- .../main/scala/cats/instances/either.scala | 6 ++-- core/src/main/scala/cats/syntax/either.scala | 36 ++++++++++--------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index c9649e926f..2bdc04655e 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -3,6 +3,7 @@ package data import cats.functor.Bifunctor import cats.instances.either._ +import cats.syntax.EitherUtil import cats.syntax.either._ /** @@ -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 }) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 71cb64515b..5acaa3795d 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -1,6 +1,7 @@ package cats package instances +import cats.syntax.EitherUtil import cats.syntax.either._ import scala.annotation.tailrec @@ -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, _))) } diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index acaa79f81b..cbac3404b8 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -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 { @@ -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 { @@ -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 { @@ -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 @@ -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]] +}