Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added leftT and improved existing lift API for EitherT #1614

Merged
merged 2 commits into from
Apr 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -236,11 +236,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] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious. Is Scalaz's trick here (add a dummy parameter to this class to make it an AnyVal; see here) necessary to make this zero-cost?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems a nice trick. Do we want to use it here first in this PR, or create another PR applying the pattern globally?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with another PR to apply it globally.

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 @@ -295,6 +362,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] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, is this better than OptionT(fopt).toRight(ifNone) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know about the OptionT.toRight, Looks like this one has slightly less function calls and an allocation to OptionT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterneyens anything else in this PR?

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