Skip to content

Commit

Permalink
added leftT and improved existing lift API for EitherT (#1614)
Browse files Browse the repository at this point in the history
* added leftT and improved existing creation API for EitherT

* added fromOptionF
  • Loading branch information
kailuowang authored Apr 24, 2017
1 parent c1dcd40 commit b6fc4e8
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 14 deletions.
88 changes: 84 additions & 4 deletions core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = bimap(identity, f)

def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] =
flatMap(b => EitherT.right[F, A, D](f(b)))
flatMap(b => EitherT.right(f(b)))

def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = bimap(f, identity)

Expand Down Expand Up @@ -232,11 +232,78 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
object EitherT extends EitherTInstances with EitherTFunctions

private[data] trait EitherTFunctions {
final def left[F[_], A, B](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left))

final def right[F[_], A, B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right))
final class LeftPartiallyApplied[B] private[EitherTFunctions] {
def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left))
}

/**
* Creates a left version of `EitherT[F, A, B]` from a `F[A]`
* {{{
* scala> import cats.data.EitherT
* scala> import cats.implicits._
* scala> EitherT.left[Int](Option("err"))
* res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Left(err)))
* }}}
*/
final def left[B]: LeftPartiallyApplied[B] = new LeftPartiallyApplied[B]

final class LeftTPartiallyApplied[F[_], B] private[EitherTFunctions] {
def apply[A](a: A)(implicit F: Applicative[F]): EitherT[F, A, B] = EitherT(F.pure(Either.left(a)))
}

/**
* Creates a left version of `EitherT[F, A, B]` from a `A`
* {{{
* scala> import cats.data.EitherT
* scala> import cats.implicits._
* scala> EitherT.leftT[Option, Int]("err")
* res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Left(err)))
* }}}
*/
final def leftT[F[_], B]: LeftTPartiallyApplied[F, B] = new LeftTPartiallyApplied[F, B]

final class RightPartiallyApplied[A] private[EitherTFunctions] {
def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right))
}

/**
* Creates a right version of `EitherT[F, A, B]` from a `F[B]`
* {{{
* scala> import cats.data.EitherT
* scala> import cats.implicits._
* scala> EitherT.right[String](Option(3))
* res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Right(3)))
* }}}
*/
final def right[A]: RightPartiallyApplied[A] = new RightPartiallyApplied[A]

final class PurePartiallyApplied[F[_], A] private[EitherTFunctions] {
def apply[B](b: B)(implicit F: Applicative[F]): EitherT[F, A, B] = right(F.pure(b))
}

/**
* Creates a new `EitherT[F, A, B]` from a `B`
* {{{
* scala> import cats.data.EitherT
* scala> import cats.implicits._
* scala> EitherT.pure[Option, String](3)
* res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Right(3)))
* }}}
*/
final def pure[F[_], A]: PurePartiallyApplied[F, A] = new PurePartiallyApplied[F, A]

/**
* Alias for [[pure]]
* {{{
* scala> import cats.data.EitherT
* scala> import cats.implicits._
* scala> EitherT.rightT[Option, String](3)
* res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Right(3)))
* }}}
*/
final def rightT[F[_], A]: PurePartiallyApplied[F, A] = pure

final def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): EitherT[F, A, B] = right(F.pure(b))

/**
* Alias for [[right]]
Expand Down Expand Up @@ -291,6 +358,19 @@ private[data] trait EitherTFunctions {
EitherT(F.pure(Either.fromOption(opt, ifNone)))
}

/** Transforms an `F[Option]` into an `EitherT`, using the second argument if the `Option` is a `None`.
* {{{
* scala> import cats.implicits._
* scala> val o: Option[Int] = None
* scala> EitherT.fromOptionF(List(o), "Answer not known.")
* res0: EitherT[List, String, Int] = EitherT(List(Left(Answer not known.)))
* scala> EitherT.fromOptionF(List(Option(42)), "Answer not known.")
* res1: EitherT[List, String, Int] = EitherT(List(Right(42)))
* }}}
*/
final def fromOptionF[F[_], E, A](fopt: F[Option[A]], ifNone: => E)(implicit F: Functor[F]): EitherT[F, E, A] =
EitherT(F.map(fopt)(opt => Either.fromOption(opt, ifNone)))

/** If the condition is satisfied, return the given `A` in `Right`
* lifted into the specified `Applicative`, otherwise, return the
* given `E` in `Left` lifted into the specified `Applicative`.
Expand Down
20 changes: 10 additions & 10 deletions tests/src/test/scala/cats/tests/EitherTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -193,33 +193,33 @@ class EitherTTests extends CatsSuite {
}

test("recover recovers handled values") {
val eithert = EitherT.left[Id, String, Int]("eithert")
val eithert = EitherT.leftT[Id, Int]("eithert")
eithert.recover { case "eithert" => 5 }.isRight should === (true)
}

test("recover ignores unhandled values") {
val eithert = EitherT.left[Id, String, Int]("eithert")
val eithert = EitherT.leftT[Id, Int]("eithert")
eithert.recover { case "noteithert" => 5 } should === (eithert)
}

test("recover ignores the right side") {
val eithert = EitherT.right[Id, String, Int](10)
val eithert = EitherT.pure[Id, String](10)
eithert.recover { case "eithert" => 5 } should === (eithert)
}

test("recoverWith recovers handled values") {
val eithert = EitherT.left[Id, String, Int]("eithert")
eithert.recoverWith { case "eithert" => EitherT.right[Id, String, Int](5) }.isRight should === (true)
val eithert = EitherT.leftT[Id, Int]("eithert")
eithert.recoverWith { case "eithert" => EitherT.pure[Id, String](5) }.isRight should === (true)
}

test("recoverWith ignores unhandled values") {
val eithert = EitherT.left[Id, String, Int]("eithert")
eithert.recoverWith { case "noteithert" => EitherT.right[Id, String, Int](5) } should === (eithert)
val eithert = EitherT.leftT[Id, Int]("eithert")
eithert.recoverWith { case "noteithert" => EitherT.pure[Id, String](5) } should === (eithert)
}

test("recoverWith ignores the right side") {
val eithert = EitherT.right[Id, String, Int](10)
eithert.recoverWith { case "eithert" => EitherT.right[Id, String, Int](5) } should === (eithert)
val eithert = EitherT.pure[Id, String](10)
eithert.recoverWith { case "eithert" => EitherT.pure[Id, String](5) } should === (eithert)
}

test("transform consistent with value.map") {
Expand Down Expand Up @@ -374,7 +374,7 @@ class EitherTTests extends CatsSuite {
test("ensure should fail if predicate not satisfied") {
forAll { (x: EitherT[Id, String, Int], s: String, p: Int => Boolean) =>
if (x.isRight && !p(x getOrElse 0)) {
x.ensure(s)(p) should === (EitherT.left[Id, String, Int](s))
x.ensure(s)(p) should === (EitherT.leftT[Id, Int](s))
}
}
}
Expand Down

0 comments on commit b6fc4e8

Please sign in to comment.