diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 267835d617..71774d6b34 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -362,6 +362,23 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { def filter(p: A => Boolean)(implicit F: Functor[F]): OptionT[F, A] = OptionT(F.map(value)(_.filter(p))) + /** + * Example: + * {{{ + * scala> import cats.data.OptionT + * + * scala> val optionT: OptionT[List, Int] = OptionT[List, Int](List(Some(100), None, Some(421), Some(333))) + * scala> optionT.filterF(n => List(n % 100 == 0, n.toString.toSet.size == 1)) + * + * res0: OptionT[List, Int] = OptionT(List(Some(100), None, None, None, None, None, Some(333))) + * }}} + */ + def filterF(p: A => F[Boolean])(implicit F: Monad[F]): OptionT[F, A] = + OptionT(F.flatMap(value) { + case v @ Some(a) => F.map(p(a)) { if (_) v else None } + case None => F.pure(None) + }) + /** * It is used for desugaring 'for comprehensions'. OptionT wouldn't work in 'for-comprehensions' without * this method. @@ -742,6 +759,14 @@ object OptionT extends OptionTInstances { def whenF[F[_], A](cond: Boolean)(fa: => F[A])(implicit F: Applicative[F]): OptionT[F, A] = if (cond) OptionT.liftF(fa) else OptionT.none[F, A] + /** + * Creates a non-empty `OptionT[F, A]` from an `F[A]` value if the given F-condition is considered `true`. + * Otherwise, `none[F, A]` is returned. Analogous to `Option.when` but for effectful conditions. + */ + def whenM[F[_], A](cond: F[Boolean])(fa: => F[A])(implicit F: Monad[F]): OptionT[F, A] = OptionT( + F.ifM(cond)(ifTrue = F.map(fa)(Some(_)), ifFalse = F.pure(None)) + ) + /** * Same as `whenF`, but expressed as a FunctionK for use with mapK. */ diff --git a/docs/nomenclature.md b/docs/nomenclature.md index d7f17d4b44..74b6dbcb24 100644 --- a/docs/nomenclature.md +++ b/docs/nomenclature.md @@ -196,7 +196,11 @@ For convenience, in these types we use the symbol `OT` to abbreviate `OptionT`. | `=> OT[F, A]` | `none` | `F: Applicative` | | `A => OT[F, A]` | `some` or `pure` | `F: Applicative` | `F[A] => OT[F, A]` | `liftF` | `F: Functor` +| `Boolean => F[A] => OT[F, A]` | `whenF` | `F: Applicative` +| `F[Boolean] => F[A] => OT[F, A]` | `whenM` | `F: Monad` | `OT[F, A] => F[Option[A]]` | `value` +| `OT[F, A] => A => Boolean => OT[F, A]` | `filter` | `F: Functor` +| `OT[F, A] => A => F[Boolean] => OT[F, A]` | `filterF` | `F: Monad` | `OT[F, A] => (A => B) => OT[F, B]` | `map` | `F: Functor` | `OT[F, A] => (F ~> G) => OT[G, B]` | `mapK` | `OT[F, A] => (A => Option[B]) => OT[F, B]` | `mapFilter` | `F: Functor` diff --git a/tests/shared/src/test/scala/cats/tests/OptionTSuite.scala b/tests/shared/src/test/scala/cats/tests/OptionTSuite.scala index f4ddcd3454..b5b9575800 100644 --- a/tests/shared/src/test/scala/cats/tests/OptionTSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/OptionTSuite.scala @@ -332,6 +332,12 @@ class OptionTSuite extends CatsSuite { } } + test("OptionT[Id, A].filterF consistent with Option.filter") { + forAll { (o: Option[Int], f: Int => Boolean) => + assert(o.filter(f) === (OptionT[Id, Int](o).filterF(f).value)) + } + } + test("OptionT[Id, A].withFilter consistent with Option.withFilter") { forAll { (o: Option[Int], f: Int => Boolean) => assert((for { x <- o if f(x) } yield x) === ((for { x <- OptionT[Id, Int](o) if f(x) } yield x).value)) @@ -378,6 +384,21 @@ class OptionTSuite extends CatsSuite { } } + test("OptionT.whenM[Id, A] consistent with Option.when") { + // Option.when is inlined here because it is not available before Scala 2.13 + def when[A]: (Boolean, A) => Option[A] = (c: Boolean, a: A) => if (c) Some(a) else None + + forAll { (i: Int, b: Boolean) => + assert(OptionT.whenM[Id, Int](b)(i).value === (when(b, i))) + } + } + + test("OptionT.whenF and OptionT.whenM consistent") { + forAll { (li: List[Int], bs: List[Boolean]) => + assert(bs.flatMap(OptionT.whenF(_)(li).value) === OptionT.whenM(bs)(li).value) + } + } + test("OptionT.whenK and OptionT.whenF consistent") { forAll { (li: List[Int], b: Boolean) => assert(IdT(li).mapK(OptionT.whenK(b)).value === (OptionT.whenF(b)(li)))