From 974cec1226402bee6e372123551d05e7fd314295 Mon Sep 17 00:00:00 2001 From: "Diego E. Alonso Blas" Date: Sat, 12 Jan 2019 17:05:28 +0000 Subject: [PATCH] Add an If-Then-Else operation to the Apply. (#2609) * Add an If-Then-Else operation to the Apply. The `FlatMap` type-class includes a function `ifM`, which allows evaluating a Boolean computation and, depending on the result, choosing between one effectful computation or other. We add to the `Apply` type-class a very similar function. Although the function is similar to `ifM`, there is an important difference between them, as to how they handle the effect: - The `ifM` will run the effects of the condition `F[Boolean]`, followed by the effects of _only one_ of the branches. - The `ifA` will always run the effect of the condition, that of the first branch, and that of the second branch, in that order. Thus, the if-then-else part in `ifA` only applies to the inner values, not to the effects. * Avoid adding methods to the type class. To preserve binary compatibility, we can not add methods to an existing type-class, and can only use the syntax extension. * Add traits for Binary Compatibility. * Attend code review comments. --- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/apply.scala | 41 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index e087eed366..c90ef1b8e7 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -81,6 +81,7 @@ trait AllSyntaxBinCompat3 extends UnorderedFoldableSyntax with Function1Syntax trait AllSyntaxBinCompat4 extends TraverseFilterSyntaxBinCompat0 + with ApplySyntaxBinCompat0 with ParallelApplySyntax with FoldableSyntaxBinCompat0 with ReducibleSyntaxBinCompat0 diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index cb62157098..3c91c6221d 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -14,6 +14,47 @@ trait ApplySyntax extends TupleSemigroupalSyntax { new ApplyOps(fa) } +trait ApplySyntaxBinCompat0 { + implicit final def catsSyntaxIfApplyOps[F[_]](fa: F[Boolean]): IfApplyOps[F] = + new IfApplyOps[F](fa) +} + +final class IfApplyOps[F[_]](private val fcond: F[Boolean]) extends AnyVal { + + /** + * An `if-then-else` lifted into the `F` context. + * This function combines the effects of the `fcond` condition and of the two branches, + * in the order in which they are given. + * + * The value of the result is, depending on the value of the condition, + * the value of the first argument, or the value of the second argument. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * + * scala> val b1: Option[Boolean] = Some(true) + * scala> val asInt1: Option[Int] = b1.ifA(Some(1), Some(0)) + * scala> asInt1.get + * res0: Int = 1 + * + * scala> val b2: Option[Boolean] = Some(false) + * scala> val asInt2: Option[Int] = b2.ifA(Some(1), Some(0)) + * scala> asInt2.get + * res1: Int = 0 + * + * scala> val b3: Option[Boolean] = Some(true) + * scala> val asInt3: Option[Int] = b3.ifA(Some(1), None) + * asInt2: Option[Int] = None + * + * }}} + */ + def ifA[A](ifTrue: F[A], ifFalse: F[A])(implicit F: Apply[F]): F[A] = { + def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse + F.ap2(F.map(fcond)(ite))(ifTrue, ifFalse) + } +} + final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { /** Alias for [[Apply.productR]]. */