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 1 commit
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
75 changes: 71 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
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