Skip to content

Commit

Permalink
Add FlatMap.forEffect (typelevel#1537)
Browse files Browse the repository at this point in the history
* Add FlatMap.forEffect

* Appease scalastyle.

* Fix 2.10 doc test error.
  • Loading branch information
cranst0n authored and kailuowang committed Apr 12, 2017
1 parent f73752c commit 9c3130e
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 0 deletions.
27 changes: 27 additions & 0 deletions core/src/main/scala/cats/FlatMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,33 @@ 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> var count = 0
* scala> val fa: Option[Int] = Some(3)
* scala> def fb: Option[Unit] = Some(count += 1)
* scala> fa.forEffectEval(Eval.later(fb))
* res0: Option[Int] = Some(3)
* scala> assert(count == 1)
* scala> none[Int].forEffectEval(Eval.later(fb))
* res1: Option[Int] = 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))

override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] =
flatMap(ff)(f => map(fa)(f))

Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/instances/tuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions laws/src/main/scala/cats/laws/FlatMapLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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] _))
}
Expand Down

0 comments on commit 9c3130e

Please sign in to comment.