Skip to content

Commit

Permalink
Add foldF, cataF and emptyflatTap to OptionT (#3335)
Browse files Browse the repository at this point in the history
* Add foldF, cataF and emptyflatMap to OptionT

* Make emptyflatTap more tolerant on type

* Rename emptyflatTap to flatTapNone, fix docs

* Add tests for new OptionT methods

* Add OptionT.semiflatTap

* fix formatting in OptionTSuite

* Rename OptionT.flatTapNone's argument for clarity

Co-authored-by: Yannick Heiber <yannick.heiber@carjump.de>
Co-authored-by: Yannick Heiber <yannick.heiber@mobimeo.com>
  • Loading branch information
3 people authored May 13, 2020
1 parent 784a358 commit cac6fdc
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 1 deletion.
33 changes: 33 additions & 0 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def fold[B](default: => B)(f: A => B)(implicit F: Functor[F]): F[B] =
F.map(value)(_.fold(default)(f))

/** Transform this `OptionT[F, A]` into a `F[C]`.
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> import cats.data.OptionT
*
* scala> val optionT: OptionT[List, Int] = OptionT[List, Int](List(Some(23), None))
* scala> optionT.foldF(Nil)(v => List(v, v * 2))
* res0: List[Int] = List(23, 46)
* }}}
*/
def foldF[B](default: => F[B])(f: A => F[B])(implicit F: FlatMap[F]): F[B] =
F.flatMap(value)(_.fold(default)(f))

/**
* Catamorphism on the Option. This is identical to [[fold]], but it only has
* one parameter list, which can result in better type inference in some
Expand All @@ -22,6 +37,14 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def cata[B](default: => B, f: A => B)(implicit F: Functor[F]): F[B] =
fold(default)(f)

/**
* Effectful catamorphism on the Option. This is identical to [[foldF]], but it only has
* one parameter list, which can result in better type inference in some
* contexts.
*/
def cataF[B](default: => F[B], f: A => F[B])(implicit F: FlatMap[F]): F[B] =
foldF(default)(f)

def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] =
OptionT(F.map(value)(_.map(f)))

Expand All @@ -43,6 +66,9 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def semiflatMap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, B] =
flatMap(a => OptionT.liftF(f(a)))

def semiflatTap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, A] =
semiflatMap(a => F.as(f(a), a))

def mapFilter[B](f: A => Option[B])(implicit F: Functor[F]): OptionT[F, B] =
subflatMap(f)

Expand All @@ -61,6 +87,13 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def subflatMap[B](f: A => Option[B])(implicit F: Functor[F]): OptionT[F, B] =
transform(_.flatMap(f))

/**
* Perform an effect if the value inside the is a `None`, leaving the value untouched. Equivalent to [[orElseF]]
* with an effect returning `None` as argument.
*/
def flatTapNone[B](ifNone: => F[B])(implicit F: Monad[F]): OptionT[F, A] =
OptionT(F.flatTap(value)(_.fold(F.void(ifNone))(_ => F.unit)))

def getOrElse[B >: A](default: => B)(implicit F: Functor[F]): F[B] =
F.map(value)(_.getOrElse(default))

Expand Down
59 changes: 58 additions & 1 deletion tests/src/test/scala/cats/tests/OptionTSuite.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package cats.tests

import cats._
import cats.data.{Const, IdT, OptionT}
import cats.data.{Const, IdT, OptionT, State}
import cats.kernel.laws.discipline.{EqTests, MonoidTests, OrderTests, PartialOrderTests, SemigroupTests}
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.syntax.applicative._
import cats.syntax.either._
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.monadError._

class OptionTSuite extends CatsSuite {
Expand Down Expand Up @@ -200,6 +203,40 @@ class OptionTSuite extends CatsSuite {
}
}

test("foldF and cataF consistent") {
forAll { (o: OptionT[List, Int], s: String, f: Int => List[String]) =>
o.foldF(List(s))(f) should ===(o.cataF(List(s), f))
}
}

test("fold and foldF consistent") {
forAll { (o: OptionT[List, Int], s: String, f: Int => String) =>
val f2 = f.andThen(i => List(i))
o.fold(s)(f) should ===(o.foldF(List(s))(f2))
}
}

test("flatTapNone doesn't change the return value") {
type TestEffect[A] = State[List[Int], A]
forAll { (optiont: OptionT[TestEffect, Int], f: TestEffect[Int], initial: List[Int]) =>
optiont.flatTapNone(f).value.runA(initial) should ===(optiont.value.runA(initial))
}
}

test("flatTapNone runs the effect") {
type TestEffect[A] = State[List[Int], A]
forAll { (optiont: OptionT[TestEffect, Int], f: TestEffect[Int], initial: List[Int]) =>
optiont.flatTapNone(f).value.runS(initial) should ===(
optiont.value
.flatTap {
case Some(v) => v.pure[TestEffect]
case None => f
}
.runS(initial)
)
}
}

test("OptionT[Id, A].fold consistent with Option.fold") {
forAll { (o: Option[Int], s: String, f: Int => String) =>
o.fold(s)(f) should ===(OptionT[Id, Int](o).fold(s)(f))
Expand Down Expand Up @@ -417,6 +454,26 @@ class OptionTSuite extends CatsSuite {
}
}

test("semiflatTap consistent with semiflatMap") {
forAll { (o: OptionT[List, Int], f: Int => List[String]) =>
o.semiflatMap(v => f(v).as(v)) should ===(o.semiflatTap(f))
}
}

test("semiflatTap does not change the return value") {
type TestEffect[A] = State[List[Int], A]
forAll { (optiont: OptionT[TestEffect, Int], f: Int => TestEffect[Int], initial: List[Int]) =>
optiont.semiflatTap(v => f(v)).value.runA(initial) should ===(optiont.value.runA(initial))
}
}

test("semiflatTap runs the effect") {
type TestEffect[A] = State[List[Int], A]
forAll { (optiont: OptionT[TestEffect, Int], f: Int => TestEffect[Int], initial: List[Int]) =>
optiont.semiflatTap(v => f(v)).value.runS(initial) should ===(optiont.semiflatMap(f).value.runS(initial))
}
}

test("subflatMap consistent with value.map+flatMap") {
forAll { (o: OptionT[List, Int], f: Int => Option[String]) =>
o.subflatMap(f) should ===(OptionT(o.value.map(_.flatMap(f))))
Expand Down

0 comments on commit cac6fdc

Please sign in to comment.