From e44e3d095211b465a690ab7c0a7698fc20fecc7e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 7 Oct 2017 17:48:52 +0200 Subject: [PATCH 1/4] Deprecate >> and << --- core/src/main/scala/cats/Apply.scala | 8 ++++++++ core/src/main/scala/cats/Cartesian.scala | 6 ++++++ core/src/main/scala/cats/FlatMap.scala | 8 -------- core/src/main/scala/cats/syntax/cartesian.scala | 6 ------ core/src/main/scala/cats/syntax/flatMap.scala | 12 ++++++++++++ docs/src/main/tut/faq.md | 2 -- free/src/test/scala/cats/free/FreeTests.scala | 2 +- tests/src/test/scala/cats/tests/MonadTest.scala | 2 +- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index e0255a5d85..4d02e38e30 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -19,6 +19,14 @@ trait Apply[F[_]] extends Functor[F] with Cartesian[F] with ApplyArityFunctions[ override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ap(map(fa)(a => (b: B) => (a, b)))(fb) + /** Sequentially compose two actions, discarding any value produced by the first. */ + def followedBy[A, B](fa: F[A])(fb: F[B]): F[B] = + map(product(fa, fb)) { case (_, b) => b } + + /** Sequentially compose two actions, discarding any value produced by the second. */ + def forEffect[A, B](fa: F[A])(fb: F[B]): F[A] = + map(product(fa, fb)) { case (a, _) => a } + /** * ap2 is a binary version of ap, defined in terms of ap. */ diff --git a/core/src/main/scala/cats/Cartesian.scala b/core/src/main/scala/cats/Cartesian.scala index a3543d009a..93d76c9825 100644 --- a/core/src/main/scala/cats/Cartesian.scala +++ b/core/src/main/scala/cats/Cartesian.scala @@ -14,6 +14,12 @@ import simulacrum.typeclass */ @typeclass trait Cartesian[F[_]] { def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] + + @inline final def *>[A, B](fa: F[A])(fb: F[B])(implicit F: Functor[F]): F[B] = + F.map(product(fa, fb)) { case (_, b) => b } + + @inline final def <*[A, B](fa: F[A])(fb: F[B])(implicit F: Functor[F]): F[A] = + F.map(product(fa, fb)) { case (a, _) => a } } object Cartesian extends CartesianArityFunctions diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 46956300be..ae687ba01a 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -44,11 +44,7 @@ import simulacrum.typeclass def flatten[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(fa => fa) - /** Sequentially compose two actions, discarding any value produced by the first. */ - def followedBy[A, B](fa: F[A])(fb: F[B]): F[B] = flatMap(fa)(_ => fb) - /** Alias for [[followedBy]]. */ - @inline final def >>[A, B](fa: F[A])(fb: F[B]): F[B] = followedBy(fa)(fb) /** * Sequentially compose two actions, discarding any value produced by the first. This variant of @@ -66,11 +62,7 @@ import simulacrum.typeclass */ def followedByEval[A, B](fa: F[A])(fb: Eval[F[B]]): F[B] = flatMap(fa)(_ => fb.value) - /** Sequentially compose two actions, discarding any value produced by the second. */ - def forEffect[A, B](fa: F[A])(fb: F[B]): F[A] = flatMap(fa)(a => map(fb)(_ => a)) - /** Alias for [[forEffect]]. */ - @inline final def <<[A, B](fa: F[A])(fb: F[B]): F[A] = forEffect(fa)(fb) /** * Sequentially compose two actions, discarding any value produced by the second. This variant of diff --git a/core/src/main/scala/cats/syntax/cartesian.scala b/core/src/main/scala/cats/syntax/cartesian.scala index e2cb025588..cd2a13b1dc 100644 --- a/core/src/main/scala/cats/syntax/cartesian.scala +++ b/core/src/main/scala/cats/syntax/cartesian.scala @@ -17,10 +17,4 @@ abstract class CartesianOps[F[_], A] extends Cartesian.Ops[F, A] { final def |@|[B](fb: F[B]): CartesianBuilder[F]#CartesianBuilder2[A, B] = new CartesianBuilder[F] |@| self |@| fb - final def *>[B](fb: F[B])(implicit F: Functor[F]): F[B] = - F.map(typeClassInstance.product(self, fb)) { case (_, b) => b } - - final def <*[B](fb: F[B])(implicit F: Functor[F]): F[A] = - F.map(typeClassInstance.product(self, fb)) { case (a, _) => a } - } diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index fb0e6d226a..4342b42788 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -11,6 +11,18 @@ trait FlatMapSyntax extends FlatMap.ToFlatMapOps { implicit final def catsSyntaxFlatMapIdOps[A](a: A): FlatMapIdOps[A] = new FlatMapIdOps[A](a) + + implicit final def catsSyntaxFlatMapOps[F[_]: FlatMap, A](fa: F[A]): FlatMapOps[F, A] = + new FlatMapOps[F, A](fa) +} + +final class FlatMapOps[F[_], A](val fa: F[A]) extends AnyVal { + + @deprecated("Use *> instead", "1.0.0-RC1") + def >>[B](fb: F[B])(implicit F: FlatMap[F]): F[B] = F.followedBy(fa)(fb) + + @deprecated("Use <* instead", "1.0.0-RC1") + def <<[B](fb: F[B])(implicit F: FlatMap[F]): F[A] = F.forEffect(fa)(fb) } final class FlattenOps[F[_], A](val ffa: F[F[A]]) extends AnyVal { diff --git a/docs/src/main/tut/faq.md b/docs/src/main/tut/faq.md index 0fdaf8f3ef..70b5fbdd54 100644 --- a/docs/src/main/tut/faq.md +++ b/docs/src/main/tut/faq.md @@ -215,8 +215,6 @@ All other symbols can be imported with `import cats.implicits._` | `x === y` | equals | | `Eq[A]` | `eqv(x: A, y: A): Boolean` | | `x =!= y` | not equals | | `Eq[A]` | `neqv(x: A, y: A): Boolean` | | `fa >>= f` | flatMap | | `FlatMap[F[_]]` | `flatMap(fa: F[A])(f: A => F[B]): F[B]` | -| `fa >> fb` | followed by | | `FlatMap[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | -| `fa << fb` | for effect | | `FlatMap[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | | x |-| y | remove | | `Group[A]` | `remove(x: A, y: A): A` | | `x > y` | greater than | | `PartialOrder[A]` | `gt(x: A, y: A): Boolean` | | `x >= y` | greater than or equal | | `PartialOrder[A]` | `gteq(x: A, y: A): Boolean` | diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 0eda77a71e..ce3bdd37cd 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -189,7 +189,7 @@ class FreeTests extends CatsSuite { 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) + val res = distr[T, Int](expr1 >>= (_ => expr2)) res == Some(Free.pure(x + y)) should ===(true) } } diff --git a/tests/src/test/scala/cats/tests/MonadTest.scala b/tests/src/test/scala/cats/tests/MonadTest.scala index 900f1bb298..3232174ac2 100644 --- a/tests/src/test/scala/cats/tests/MonadTest.scala +++ b/tests/src/test/scala/cats/tests/MonadTest.scala @@ -10,7 +10,7 @@ class MonadTest extends CatsSuite { val smallPosInt = Gen.choose(1, 5000) val increment: StateT[Id, Int, Unit] = StateT.modify(_ + 1) - val incrementAndGet: StateT[Id, Int, Int] = increment >> StateT.get + val incrementAndGet: StateT[Id, Int, Int] = increment *> StateT.get test("whileM_") { forAll(smallPosInt) { (max: Int) => From b8a302d43e0bf2eecadbd19b9daeb42af0264f75 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 8 Oct 2017 12:41:50 +0200 Subject: [PATCH 2/4] Move *> and <* to Apply and make them aliases --- core/src/main/scala/cats/Apply.scala | 16 ++++++++++++---- core/src/main/scala/cats/Cartesian.scala | 6 ------ docs/src/main/tut/faq.md | 6 ++++-- free/src/test/scala/cats/free/FreeTests.scala | 4 ++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 4d02e38e30..0d28130263 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -19,13 +19,21 @@ trait Apply[F[_]] extends Functor[F] with Cartesian[F] with ApplyArityFunctions[ override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ap(map(fa)(a => (b: B) => (a, b)))(fb) - /** Sequentially compose two actions, discarding any value produced by the first. */ + /** Compose two actions, discarding any value produced by the first. */ def followedBy[A, B](fa: F[A])(fb: F[B]): F[B] = - map(product(fa, fb)) { case (_, b) => b } + map2(fa, fb)((_, b) => b) - /** Sequentially compose two actions, discarding any value produced by the second. */ + /** Alias for [[followedBy]]. */ + @inline final def *>[A, B](fa: F[A])(fb: F[B]): F[B] = + followedBy(fa)(fb) + + /** Compose two actions, discarding any value produced by the second. */ def forEffect[A, B](fa: F[A])(fb: F[B]): F[A] = - map(product(fa, fb)) { case (a, _) => a } + map2(fa, fb)((a, _) => a) + + /** Alias for [[forEffect]]. */ + @inline final def <*[A, B](fa: F[A])(fb: F[B]): F[A] = + forEffect(fa)(fb) /** * ap2 is a binary version of ap, defined in terms of ap. diff --git a/core/src/main/scala/cats/Cartesian.scala b/core/src/main/scala/cats/Cartesian.scala index 93d76c9825..a3543d009a 100644 --- a/core/src/main/scala/cats/Cartesian.scala +++ b/core/src/main/scala/cats/Cartesian.scala @@ -14,12 +14,6 @@ import simulacrum.typeclass */ @typeclass trait Cartesian[F[_]] { def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] - - @inline final def *>[A, B](fa: F[A])(fb: F[B])(implicit F: Functor[F]): F[B] = - F.map(product(fa, fb)) { case (_, b) => b } - - @inline final def <*[A, B](fa: F[A])(fb: F[B])(implicit F: Functor[F]): F[A] = - F.map(product(fa, fb)) { case (a, _) => a } } object Cartesian extends CartesianArityFunctions diff --git a/docs/src/main/tut/faq.md b/docs/src/main/tut/faq.md index 70b5fbdd54..b6eddfb462 100644 --- a/docs/src/main/tut/faq.md +++ b/docs/src/main/tut/faq.md @@ -210,8 +210,8 @@ All other symbols can be imported with `import cats.implicits._` | Symbol | Name | Nickname | Type Class | Signature | | -------------------------------- | ---------------------- | ---------------- | ----------------------- | --------------------------------------------------------- | -| `fa *> fb` | right apply | | `Cartesian[F[_]]` | `*>(fa: F[A])(fb: F[B]): F[B]` | -| `fa <* fb` | left apply | | `Cartesian[F[_]]` | `<*(fa: F[A])(fb: F[B]): F[A]` | +| `fa *> fb` | followed by | | `Apply[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | +| `fa <* fb` | for effect | | `Apply[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | | `x === y` | equals | | `Eq[A]` | `eqv(x: A, y: A): Boolean` | | `x =!= y` | not equals | | `Eq[A]` | `neqv(x: A, y: A): Boolean` | | `fa >>= f` | flatMap | | `FlatMap[F[_]]` | `flatMap(fa: F[A])(f: A => F[B]): F[B]` | @@ -229,6 +229,8 @@ All other symbols can be imported with `import cats.implicits._` | `F :≺: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | | `⊥` | bottom | | N/A | `Nothing` | | `⊤` | top | | N/A | `Any` | +| `fa >> fb` (Deprecated) | followed by | | `FlatMap[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | +| `fa << fb` (Deprecated) | for effect | | `FlatMap[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | ## How can I help? diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index ce3bdd37cd..d8ee9c2ba7 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -189,8 +189,8 @@ class FreeTests extends CatsSuite { 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) + val res = distr[T, Int](expr1 *> expr2) + res.map(_.foldMap(eitherKInterpreter)) should === (Some(Free.pure[Id, Int](x + y).foldMap(FunctionK.id))) } } } From 8f35654f1b6956b1ded3a6e9390f88f93ebf6640 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 8 Oct 2017 22:24:21 +0200 Subject: [PATCH 3/4] Add syntaxTests for *> and <* --- tests/src/test/scala/cats/tests/SyntaxTests.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 55321d5184..1d8006f3d8 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -209,6 +209,9 @@ object SyntaxTests extends AllInstances with AllSyntax { val f = mock[(A, B, C) => Z] val ff = mock[F[(A, B, C) => Z]] + fa *> fb + fb <* fc + tfabc mapN f (fa, fb, fc) mapN f (fa, fb, fc) apWith ff From 95ac1abc96fa11baabab4da2e187c509416fc75b Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 9 Oct 2017 19:44:01 +0200 Subject: [PATCH 4/4] Move laws --- laws/src/main/scala/cats/laws/ApplyLaws.scala | 6 ++++++ laws/src/main/scala/cats/laws/FlatMapLaws.scala | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 760e7c3330..34e2ee4773 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -20,6 +20,12 @@ trait ApplyLaws[F[_]] extends FunctorLaws[F] with CartesianLaws[F] { def map2EvalConsistency[A, B, C](fa: F[A], fb: F[B], f: (A, B) => C): IsEq[F[C]] = F.map2(fa, fb)(f) <-> (F.map2Eval(fa, Eval.now(fb))(f).value) + + def followedByConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = + F.followedBy(fa)(fb) <-> F.map2(fa, fb)((_, b) => b) + + def forEffectConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[A]] = + F.forEffect(fa)(fb) <-> F.map2(fa, fb)((a, _) => a) } object ApplyLaws { diff --git a/laws/src/main/scala/cats/laws/FlatMapLaws.scala b/laws/src/main/scala/cats/laws/FlatMapLaws.scala index 0eb8bf2b8a..93d0da15af 100644 --- a/laws/src/main/scala/cats/laws/FlatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/FlatMapLaws.scala @@ -18,11 +18,6 @@ trait FlatMapLaws[F[_]] extends ApplyLaws[F] { def flatMapConsistentApply[A, B](fa: F[A], fab: F[A => B]): IsEq[F[B]] = fab.ap(fa) <-> fab.flatMap(f => fa.map(f)) - def followedByConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = - F.followedBy(fa)(fb) <-> F.flatMap(fa)(_ => fb) - - def forEffectConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[A]] = - F.forEffect(fa)(fb) <-> F.flatMap(fa)(a => fb.map(_ => a)) /** * The composition of `cats.data.Kleisli` arrows is associative. This is