From 003141e6dc4d0228a5d957acd98f47c15066cd8a Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Sun, 12 Mar 2017 21:31:01 -0600 Subject: [PATCH 1/5] Improvements to Inject. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - move it back to cats-core (near `Coproduct`) - make `inj`/`prj` natural transformations - add `apply`/`unapply` (lowered `inj`/`prj`) - define `Free.roll` - move `injectRoll` (née `Inject.inject`) and `match_` to `Free` - reintroduce "null identity" test (written by @edmundnoble) --- core/src/main/scala/cats/Inject.scala | 47 ++++++++ core/src/main/scala/cats/package.scala | 5 + free/src/main/scala/cats/free/Free.scala | 12 ++ free/src/main/scala/cats/free/Inject.scala | 49 --------- free/src/main/scala/cats/free/package.scala | 7 -- free/src/test/scala/cats/free/FreeTests.scala | 86 ++++++++++++++- .../test/scala/cats/free/InjectTests.scala | 104 ------------------ .../test/scala/cats/tests/InjectTests.scala | 81 ++++++++++++++ 8 files changed, 230 insertions(+), 161 deletions(-) create mode 100644 core/src/main/scala/cats/Inject.scala delete mode 100644 free/src/main/scala/cats/free/Inject.scala delete mode 100644 free/src/test/scala/cats/free/InjectTests.scala create mode 100644 tests/src/test/scala/cats/tests/InjectTests.scala diff --git a/core/src/main/scala/cats/Inject.scala b/core/src/main/scala/cats/Inject.scala new file mode 100644 index 0000000000..a6012e7b97 --- /dev/null +++ b/core/src/main/scala/cats/Inject.scala @@ -0,0 +1,47 @@ +package cats + +import cats.arrow.FunctionK +import cats.data.Coproduct +import cats.syntax.either._ + +/** + * Inject type class as described in "Data types a la carte" (Swierstra 2008). + * + * @see [[http://www.staff.science.uu.nl/~swier004/publications/2008-jfp.pdf]] + */ +sealed abstract class Inject[F[_], G[_]] { + def inj: FunctionK[F, G] + + def prj: FunctionK[G, λ[α => Option[F[α]]]] + + def apply[A](fa: F[A]): G[A] = inj(fa) + + def unapply[A](ga: G[A]): Option[F[A]] = prj(ga) +} + +private[cats] sealed abstract class InjectInstances { + implicit def catsReflexiveInjectInstance[F[_]]: Inject[F, F] = + new Inject[F, F] { + val inj = λ[FunctionK[F, F]](identity(_)) + + val prj = λ[FunctionK[F, λ[α => Option[F[α]]]]](Some(_)) + } + + implicit def catsLeftInjectInstance[F[_], G[_]]: Inject[F, Coproduct[F, G, ?]] = + new Inject[F, Coproduct[F, G, ?]] { + val inj = λ[FunctionK[F, Coproduct[F, G, ?]]](Coproduct.leftc(_)) + + val prj = λ[FunctionK[Coproduct[F, G, ?], λ[α => Option[F[α]]]]](_.run.swap.toOption) + } + + implicit def catsRightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]): Inject[F, Coproduct[H, G, ?]] = + new Inject[F, Coproduct[H, G, ?]] { + val inj = λ[FunctionK[G, Coproduct[H, G, ?]]](Coproduct.rightc(_)) compose I.inj + + val prj = λ[FunctionK[Coproduct[H, G, ?], λ[α => Option[F[α]]]]](_.run.toOption.flatMap(I.prj(_))) + } +} + +object Inject extends InjectInstances { + def apply[F[_], G[_]](implicit I: Inject[F, G]): Inject[F, G] = I +} diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 8fefc3a42b..509536c8a6 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -10,6 +10,11 @@ package object cats { type ⊥ = Nothing type ⊤ = Any + /** [[cats.Inject]][F, G] */ + type :<:[F[_], G[_]] = Inject[F, G] + + /** [[cats.Inject]][F, G] */ + type :≺:[F[_], G[_]] = Inject[F, G] /** * Identity, encoded as `type Id[A] = A`, a convenient alias to make diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index a79a01aa5d..e4e9a8e124 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -184,6 +184,12 @@ object Free { */ def liftF[F[_], A](value: F[A]): Free[F, A] = Suspend(value) + /** + * Absorb a step into the free monad. + */ + def roll[F[_], A](value: F[Free[F, A]]): Free[F, A] = + liftF(value).flatMap(identity) + /** * Suspend the creation of a `Free[F, A]` value. */ @@ -221,6 +227,12 @@ object Free { Free.liftF(I.inj(fa)) } + def injectRoll[F[_], G[_], A](ga: G[Free[F, A]])(implicit I: Inject[G, F]): Free[F, A] = + Free.roll(I.inj(ga)) + + def match_[F[_], G[_], A](fa: Free[F, A])(implicit F: Functor[F], I: Inject[G, F]): Option[G[Free[F, A]]] = + fa.resume.fold(I.prj(_), _ => None) + /** * `Free[S, ?]` has a monad for any type constructor `S[_]`. */ diff --git a/free/src/main/scala/cats/free/Inject.scala b/free/src/main/scala/cats/free/Inject.scala deleted file mode 100644 index efa1fdc36e..0000000000 --- a/free/src/main/scala/cats/free/Inject.scala +++ /dev/null @@ -1,49 +0,0 @@ -package cats.free - -import cats.Functor -import cats.data.Coproduct - - -/** - * Inject type class as described in "Data types a la carte" (Swierstra 2008). - * - * @see [[http://www.staff.science.uu.nl/~swier004/publications/2008-jfp.pdf]] - */ -sealed abstract class Inject[F[_], G[_]] { - def inj[A](fa: F[A]): G[A] - - def prj[A](ga: G[A]): Option[F[A]] -} - -private[free] sealed abstract class InjectInstances { - implicit def catsFreeReflexiveInjectInstance[F[_]]: Inject[F, F] = - new Inject[F, F] { - def inj[A](fa: F[A]): F[A] = fa - - def prj[A](ga: F[A]): Option[F[A]] = Some(ga) - } - - implicit def catsFreeLeftInjectInstance[F[_], G[_]]: Inject[F, Coproduct[F, G, ?]] = - new Inject[F, Coproduct[F, G, ?]] { - def inj[A](fa: F[A]): Coproduct[F, G, A] = Coproduct.leftc(fa) - - def prj[A](ga: Coproduct[F, G, A]): Option[F[A]] = ga.run.fold(Some(_), _ => None) - } - - implicit def catsFreeRightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]): Inject[F, Coproduct[H, G, ?]] = - new Inject[F, Coproduct[H, G, ?]] { - def inj[A](fa: F[A]): Coproduct[H, G, A] = Coproduct.rightc(I.inj(fa)) - - def prj[A](ga: Coproduct[H, G, A]): Option[F[A]] = ga.run.fold(_ => None, I.prj) - } -} - -object Inject extends InjectInstances { - def inject[F[_], G[_], A](ga: G[Free[F, A]])(implicit I: Inject[G, F]): Free[F, A] = - Free.liftF(I.inj(ga)) flatMap identity - - def match_[F[_], G[_], A](fa: Free[F, A])(implicit F: Functor[F], I: Inject[G, F]): Option[G[Free[F, A]]] = - fa.resume.fold(I.prj, _ => None) - - def apply[F[_], G[_]](implicit I: Inject[F, G]): Inject[F, G] = I -} diff --git a/free/src/main/scala/cats/free/package.scala b/free/src/main/scala/cats/free/package.scala index dd7d36a8e8..2942a76ac4 100644 --- a/free/src/main/scala/cats/free/package.scala +++ b/free/src/main/scala/cats/free/package.scala @@ -4,11 +4,4 @@ package object free { /** Alias for the free monad over the `Function0` functor. */ type Trampoline[A] = Free[Function0, A] object Trampoline extends TrampolineFunctions - - /** [[cats.free.Inject]][F, G] */ - type :<:[F[_], G[_]] = Inject[F, G] - - /** [[cats.free.Inject]][F, G] */ - type :≺:[F[_], G[_]] = Inject[F, G] - } diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index cfc98a475b..4054805cca 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -1,10 +1,11 @@ package cats package free -import cats.tests.CatsSuite import cats.arrow.FunctionK +import cats.data.Coproduct import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary.catsLawsArbitraryForFn0 +import cats.tests.CatsSuite import org.scalacheck.{Arbitrary, Gen, Cogen} import Arbitrary.arbFunction1 @@ -107,6 +108,89 @@ class FreeTests extends CatsSuite { } assert(res == Either.left(100000)) } + + sealed trait Test1Algebra[A] + + case class Test1[A](value : Int, f: Int => A) extends Test1Algebra[A] + + object Test1Algebra { + implicit def test1AlgebraAFunctor: Functor[Test1Algebra] = + new Functor[Test1Algebra] { + def map[A, B](a: Test1Algebra[A])(f: A => B): Test1Algebra[B] = a match { + case Test1(k, h) => Test1(k, x => f(h(x))) + } + } + + implicit def test1AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test1Algebra[A]] = + Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test1(s, f)) + } + + sealed trait Test2Algebra[A] + + case class Test2[A](value : Int, f: Int => A) extends Test2Algebra[A] + + object Test2Algebra { + implicit def test2AlgebraAFunctor: Functor[Test2Algebra] = + new Functor[Test2Algebra] { + def map[A, B](a: Test2Algebra[A])(f: A => B): Test2Algebra[B] = a match { + case Test2(k, h) => Test2(k, x => f(h(x))) + } + } + + implicit def test2AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2Algebra[A]] = + Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f)) + } + + type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] + + object Test1Interpreter extends FunctionK[Test1Algebra,Id] { + override def apply[A](fa: Test1Algebra[A]): Id[A] = fa match { + case Test1(k, h) => h(k) + } + } + + object Test2Interpreter extends FunctionK[Test2Algebra,Id] { + override def apply[A](fa: Test2Algebra[A]): Id[A] = fa match { + case Test2(k, h) => h(k) + } + } + + val coProductInterpreter: FunctionK[T,Id] = Test1Interpreter or Test2Interpreter + + test(".inject") { + forAll { (x: Int, y: Int) => + def res[F[_]] + (implicit I0: Test1Algebra :<: F, + I1: Test2Algebra :<: F): Free[F, Int] = { + for { + a <- Free.inject[Test1Algebra, F](Test1(x, identity)) + b <- Free.inject[Test2Algebra, F](Test2(y, identity)) + } yield a + b + } + (res[T] foldMap coProductInterpreter) == (x + y) should ===(true) + } + } + + val x: Free[T, Int] = Free.inject[Test1Algebra, T](Test1(1, identity)) + + test(".injectRoll") { + def distr[F[_], A](f: Free[F, A]) + (implicit + F: Functor[F], + I0: Test1Algebra :<: F, + I1: Test2Algebra :<: F): Option[Free[F, A]] = + for { + Test1(x, h) <- Free.match_[F, Test1Algebra, A](f) + Test2(y, k) <- Free.match_[F, Test2Algebra, A](h(x)) + } yield k(x + y) + + forAll { (x: Int, y: Int) => + val expr1: Free[T, Int] = Free.injectRoll[T, Test1Algebra, Int](Test1(x, Free.pure)) + val expr2: Free[T, Int] = Free.injectRoll[T, Test2Algebra, Int](Test2(y, Free.pure)) + val res = distr[T, Int](expr1 >> expr2) + res == Some(Free.pure(x + y)) should ===(true) + } + } } object FreeTests extends FreeTestsInstances { diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala deleted file mode 100644 index 8e1a97dcaf..0000000000 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ /dev/null @@ -1,104 +0,0 @@ -package cats -package free - -import cats.arrow.FunctionK -import cats.tests.CatsSuite -import cats.data.Coproduct -import org.scalacheck._ - -class InjectTests extends CatsSuite { - - import Inject._ - - sealed trait Test1Algebra[A] - - case class Test1[A](value : Int, f: Int => A) extends Test1Algebra[A] - - sealed trait Test2Algebra[A] - - case class Test2[A](value : Int, f: Int => A) extends Test2Algebra[A] - - type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] - - implicit def test1AlgebraAFunctor: Functor[Test1Algebra] = - new Functor[Test1Algebra] { - def map[A, B](a: Test1Algebra[A])(f: A => B): Test1Algebra[B] = a match { - case Test1(k, h) => Test1(k, x => f(h(x))) - } - } - - implicit def test2AlgebraAFunctor: Functor[Test2Algebra] = - new Functor[Test2Algebra] { - def map[A, B](a: Test2Algebra[A])(f: A => B): Test2Algebra[B] = a match { - case Test2(k, h) => Test2(k, x => f(h(x))) - } - } - - implicit def test1Arbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test1[A]] = - Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test1(s, f)) - - implicit def test2Arbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2[A]] = - Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f)) - - object Test1Interpreter extends FunctionK[Test1Algebra,Id] { - override def apply[A](fa: Test1Algebra[A]): Id[A] = fa match { - case Test1(k, h) => h(k) - } - } - - object Test2Interpreter extends FunctionK[Test2Algebra,Id] { - override def apply[A](fa: Test2Algebra[A]): Id[A] = fa match { - case Test2(k, h) => h(k) - } - } - - val coProductInterpreter: FunctionK[T,Id] = Test1Interpreter or Test2Interpreter - - val x: Free[T, Int] = Free.inject[Test1Algebra, T](Test1(1, identity)) - - test("inj") { - forAll { (x: Int, y: Int) => - def res[F[_]] - (implicit I0: Test1Algebra :<: F, - I1: Test2Algebra :<: F): Free[F, Int] = { - for { - a <- Free.inject[Test1Algebra, F](Test1(x, identity)) - b <- Free.inject[Test2Algebra, F](Test2(y, identity)) - } yield a + b - } - (res[T] foldMap coProductInterpreter) == (x + y) should ===(true) - } - } - - test("prj") { - def distr[F[_], A](f: Free[F, A]) - (implicit - F: Functor[F], - I0: Test1Algebra :<: F, - I1: Test2Algebra :<: F): Option[Free[F, A]] = - for { - Test1(x, h) <- match_[F, Test1Algebra, A](f) - Test2(y, k) <- match_[F, Test2Algebra, A](h(x)) - } yield k(x + y) - - forAll { (x: Int, y: Int) => - val expr1: Free[T, Int] = Inject.inject[T, Test1Algebra, Int](Test1(x, Free.pure)) - val expr2: Free[T, Int] = Inject.inject[T, Test2Algebra, Int](Test2(y, Free.pure)) - val res = distr[T, Int](expr1 >> expr2) - res == Some(Free.pure(x + y)) should ===(true) - } - } - - test("apply in left") { - forAll { (y: Test1[Int]) => - Inject[Test1Algebra, T].inj(y) == Coproduct(Left(y)) should ===(true) - } - } - - test("apply in right") { - forAll { (y: Test2[Int]) => - Inject[Test2Algebra, T].inj(y) == Coproduct(Right(y)) should ===(true) - } - } - -} diff --git a/tests/src/test/scala/cats/tests/InjectTests.scala b/tests/src/test/scala/cats/tests/InjectTests.scala new file mode 100644 index 0000000000..0372b9c8f6 --- /dev/null +++ b/tests/src/test/scala/cats/tests/InjectTests.scala @@ -0,0 +1,81 @@ +package cats + +// import cats.arrow.FunctionK +import cats.data.Coproduct +import cats.tests.CatsSuite +import org.scalacheck._ + +class InjectTests extends CatsSuite { + + sealed trait Test1Algebra[A] + + case class Test1[A](value : Int, f: Int => A) extends Test1Algebra[A] + + object Test1Algebra { + implicit def test1AlgebraAFunctor: Functor[Test1Algebra] = + new Functor[Test1Algebra] { + def map[A, B](a: Test1Algebra[A])(f: A => B): Test1Algebra[B] = a match { + case Test1(k, h) => Test1(k, x => f(h(x))) + } + } + + implicit def test1AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test1Algebra[A]] = + Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test1(s, f)) + } + + sealed trait Test2Algebra[A] + + case class Test2[A](value : Int, f: Int => A) extends Test2Algebra[A] + + object Test2Algebra { + implicit def test2AlgebraAFunctor: Functor[Test2Algebra] = + new Functor[Test2Algebra] { + def map[A, B](a: Test2Algebra[A])(f: A => B): Test2Algebra[B] = a match { + case Test2(k, h) => Test2(k, x => f(h(x))) + } + } + + implicit def test2AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2Algebra[A]] = + Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f)) + } + + type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] + + test("inj & prj") { + def distr[F[_], A](f1: F[A], f2: F[A]) + (implicit + F: Functor[F], + I0: Test1Algebra :<: F, + I1: Test2Algebra :<: F): Option[Int] = + for { + Test1(x, _) <- I0.prj(f1) + Test2(y, _) <- I1.prj(f2) + } yield x + y + + forAll { (x: Int, y: Int) => + val expr1: T[Int] = Inject[Test1Algebra, T].inj(Test1(x, _ + 1)) + val expr2: T[Int] = Inject[Test2Algebra, T].inj(Test2(y, _ * 2)) + val res = distr[T, Int](expr1, expr2) + res should ===(Some(x + y)) + } + } + + test("apply in left") { + forAll { (y: Test1Algebra[Int]) => + Inject[Test1Algebra, T].inj(y) == Coproduct(Left(y)) should ===(true) + } + } + + test("apply in right") { + forAll { (y: Test2Algebra[Int]) => + Inject[Test2Algebra, T].inj(y) == Coproduct(Right(y)) should ===(true) + } + } + + test("null identity") { + val listIntNull = null.asInstanceOf[List[Int]] + Inject.catsReflexiveInjectInstance[List].inj[Int](listIntNull) should ===(listIntNull) + Inject.catsReflexiveInjectInstance[List].prj[Int](listIntNull) should ===(Some(listIntNull)) + } + +} From 2ba81aaca132a75fdfd0bff828782ea53d711408 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Mon, 13 Mar 2017 11:56:17 -0600 Subject: [PATCH 2/5] Fix tut breakage, and remove commented import. --- docs/src/main/tut/datatypes/freemonad.md | 4 ++-- tests/src/test/scala/cats/tests/InjectTests.scala | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/main/tut/datatypes/freemonad.md b/docs/src/main/tut/datatypes/freemonad.md index c201556173..8cfa876959 100644 --- a/docs/src/main/tut/datatypes/freemonad.md +++ b/docs/src/main/tut/datatypes/freemonad.md @@ -305,8 +305,8 @@ Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` ```tut:silent import cats.data.Coproduct -import cats.free.{Inject, Free} -import cats.{Id, ~>} +import cats.free.Free +import cats.{Id, Inject, ~>} import scala.collection.mutable.ListBuffer ``` diff --git a/tests/src/test/scala/cats/tests/InjectTests.scala b/tests/src/test/scala/cats/tests/InjectTests.scala index 0372b9c8f6..5f9bbf8644 100644 --- a/tests/src/test/scala/cats/tests/InjectTests.scala +++ b/tests/src/test/scala/cats/tests/InjectTests.scala @@ -1,6 +1,5 @@ package cats -// import cats.arrow.FunctionK import cats.data.Coproduct import cats.tests.CatsSuite import org.scalacheck._ From d9566a215f3cf8846d79fd58b87dab19ac954a58 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 14 Mar 2017 16:37:26 -0600 Subject: [PATCH 3/5] Remove an unused import. --- core/src/main/scala/cats/Inject.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/scala/cats/Inject.scala b/core/src/main/scala/cats/Inject.scala index a6012e7b97..1eecaf87d7 100644 --- a/core/src/main/scala/cats/Inject.scala +++ b/core/src/main/scala/cats/Inject.scala @@ -2,7 +2,6 @@ package cats import cats.arrow.FunctionK import cats.data.Coproduct -import cats.syntax.either._ /** * Inject type class as described in "Data types a la carte" (Swierstra 2008). From baeec37ba963d056541ef7bba2d219bfa2a5dff6 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Thu, 16 Mar 2017 15:26:12 -0600 Subject: [PATCH 4/5] Change to not require syntax.either. Otherwise we need it for 2.10 & 2.11 and need to _not_ have it for 2.12. --- core/src/main/scala/cats/Inject.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Inject.scala b/core/src/main/scala/cats/Inject.scala index 1eecaf87d7..7963fd9cd7 100644 --- a/core/src/main/scala/cats/Inject.scala +++ b/core/src/main/scala/cats/Inject.scala @@ -30,14 +30,14 @@ private[cats] sealed abstract class InjectInstances { new Inject[F, Coproduct[F, G, ?]] { val inj = λ[FunctionK[F, Coproduct[F, G, ?]]](Coproduct.leftc(_)) - val prj = λ[FunctionK[Coproduct[F, G, ?], λ[α => Option[F[α]]]]](_.run.swap.toOption) + val prj = λ[FunctionK[Coproduct[F, G, ?], λ[α => Option[F[α]]]]](_.run.left.toOption) } implicit def catsRightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]): Inject[F, Coproduct[H, G, ?]] = new Inject[F, Coproduct[H, G, ?]] { val inj = λ[FunctionK[G, Coproduct[H, G, ?]]](Coproduct.rightc(_)) compose I.inj - val prj = λ[FunctionK[Coproduct[H, G, ?], λ[α => Option[F[α]]]]](_.run.toOption.flatMap(I.prj(_))) + val prj = λ[FunctionK[Coproduct[H, G, ?], λ[α => Option[F[α]]]]](_.run.right.toOption.flatMap(I.prj(_))) } } From 380ea8117c3704f47df961c6be0dcec594e07a1f Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Thu, 16 Mar 2017 22:07:27 -0600 Subject: [PATCH 5/5] Add tests for Inject#apply and #unapply. --- .../test/scala/cats/tests/InjectTests.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/src/test/scala/cats/tests/InjectTests.scala b/tests/src/test/scala/cats/tests/InjectTests.scala index 5f9bbf8644..943634283e 100644 --- a/tests/src/test/scala/cats/tests/InjectTests.scala +++ b/tests/src/test/scala/cats/tests/InjectTests.scala @@ -59,6 +59,25 @@ class InjectTests extends CatsSuite { } } + test("apply & unapply") { + def distr[F[_], A](f1: F[A], f2: F[A]) + (implicit + F: Functor[F], + I0: Test1Algebra :<: F, + I1: Test2Algebra :<: F): Option[Int] = + for { + Test1(x, _) <- I0.unapply(f1) + Test2(y, _) <- I1.unapply(f2) + } yield x + y + + forAll { (x: Int, y: Int) => + val expr1: T[Int] = Inject[Test1Algebra, T].apply(Test1(x, _ + 1)) + val expr2: T[Int] = Inject[Test2Algebra, T].apply(Test2(y, _ * 2)) + val res = distr[T, Int](expr1, expr2) + res should ===(Some(x + y)) + } + } + test("apply in left") { forAll { (y: Test1Algebra[Int]) => Inject[Test1Algebra, T].inj(y) == Coproduct(Left(y)) should ===(true)