From 5a5f097267b659ab9d9d7e79f85ccc8e1bb2e65d Mon Sep 17 00:00:00 2001 From: cranst0n Date: Thu, 16 Feb 2017 13:36:29 -0500 Subject: [PATCH 1/3] Add FlatMap.forEffect --- core/src/main/scala/cats/FlatMap.scala | 25 +++++++++++++++++++ .../src/main/scala/cats/instances/tuple.scala | 3 +++ .../main/scala/cats/laws/FlatMapLaws.scala | 3 +++ .../cats/laws/discipline/FlatMapTests.scala | 1 + 4 files changed, 32 insertions(+) diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 880b15cac0..46c9e77cd2 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -66,6 +66,31 @@ 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 + * [[forEffect]] also lets you define the evaluation strategy of the second action. For instance + * you can evaluate it only ''after'' the first action has finished: + * + * {{{ + * scala> import cats.Eval + * scala> import cats.implicits._ + * scala> val fa: Option[Int] = Some(3) + * scala> def fb: Option[Unit] = Some(println("effect!")) + * scala> fa.forEffectEval(Eval.later(fb)) + * effect! + * res0: Option[Int] = Some(3) + * scala> None.forEffectEval(Eval.later(fb)) + * res1: Option[Nothing] = None + * }}} + */ + def forEffectEval[A, B](fa: F[A])(fb: Eval[F[B]]): F[A] = flatMap(fa)(a => map(fb.value)(_ => a)) + override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = flatMap(ff)(f => map(fa)(f)) diff --git a/core/src/main/scala/cats/instances/tuple.scala b/core/src/main/scala/cats/instances/tuple.scala index 0e5a8816fa..b2987994ac 100644 --- a/core/src/main/scala/cats/instances/tuple.scala +++ b/core/src/main/scala/cats/instances/tuple.scala @@ -110,6 +110,9 @@ private[instances] class FlatMapTuple2[X](s: Semigroup[X]) extends FlatMap[(X, ? override def followedBy[A, B](a: (X, A))(b: (X, B)): (X, B) = (s.combine(a._1, b._1), b._2) + override def forEffect[A, B](a: (X, A))(b: (X, B)): (X, A) = + (s.combine(a._1, b._1), a._2) + override def mproduct[A, B](fa: (X, A))(f: A => (X, B)): (X, (A, B)) = { val xb = f(fa._2) val x = s.combine(fa._1, xb._1) diff --git a/laws/src/main/scala/cats/laws/FlatMapLaws.scala b/laws/src/main/scala/cats/laws/FlatMapLaws.scala index 5e50994948..d1dbc1dce4 100644 --- a/laws/src/main/scala/cats/laws/FlatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/FlatMapLaws.scala @@ -21,6 +21,9 @@ trait FlatMapLaws[F[_]] extends ApplyLaws[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 * analogous to [[flatMapAssociativity]]. diff --git a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala index 5eae4b7fba..10c72fd33e 100644 --- a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala @@ -34,6 +34,7 @@ trait FlatMapTests[F[_]] extends ApplyTests[F] { "flatMap associativity" -> forAll(laws.flatMapAssociativity[A, B, C] _), "flatMap consistent apply" -> forAll(laws.flatMapConsistentApply[A, B] _), "followedBy consistent flatMap" -> forAll(laws.followedByConsistency[A, B] _), + "forEffect consistent flatMap" -> forAll(laws.forEffectConsistency[A, B] _), "mproduct consistent flatMap" -> forAll(laws.mproductConsistency[A, B] _), "tailRecM consistent flatMap" -> forAll(laws.tailRecMConsistentFlatMap[A] _)) } From 8791bd64f085da05cbe72cde727e72908f338678 Mon Sep 17 00:00:00 2001 From: cranst0n Date: Thu, 16 Feb 2017 14:24:59 -0500 Subject: [PATCH 2/3] Appease scalastyle. --- core/src/main/scala/cats/FlatMap.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 46c9e77cd2..2318f6f83b 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -80,13 +80,15 @@ import simulacrum.typeclass * {{{ * scala> import cats.Eval * scala> import cats.implicits._ + * scala> var count = 0 * scala> val fa: Option[Int] = Some(3) - * scala> def fb: Option[Unit] = Some(println("effect!")) + * scala> def fb: Option[Unit] = Some(count += 1) * scala> fa.forEffectEval(Eval.later(fb)) - * effect! * res0: Option[Int] = Some(3) + * scala> assert(count == 1) * scala> None.forEffectEval(Eval.later(fb)) * res1: Option[Nothing] = None + * scala> assert(count == 1) * }}} */ def forEffectEval[A, B](fa: F[A])(fb: Eval[F[B]]): F[A] = flatMap(fa)(a => map(fb.value)(_ => a)) From 2da3ac334ebdd06f3d04b19c9d8879b1bbd50d2e Mon Sep 17 00:00:00 2001 From: cranst0n Date: Fri, 7 Apr 2017 14:17:51 -0400 Subject: [PATCH 3/3] Fix 2.10 doc test error. --- core/src/main/scala/cats/FlatMap.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 2318f6f83b..46956300be 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -86,8 +86,8 @@ import simulacrum.typeclass * scala> fa.forEffectEval(Eval.later(fb)) * res0: Option[Int] = Some(3) * scala> assert(count == 1) - * scala> None.forEffectEval(Eval.later(fb)) - * res1: Option[Nothing] = None + * scala> none[Int].forEffectEval(Eval.later(fb)) + * res1: Option[Int] = None * scala> assert(count == 1) * }}} */