diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 27546046564..b00d6ed8472 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -1,6 +1,8 @@ package cats package data +import std.option.{catsStdInstancesForOption => optionInstance} + /** * `OptionT[F[_], A]` is a light wrapper on an `F[Option[A]]` with some * convenient methods for working with this nested structure. @@ -29,11 +31,7 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { flatMapF(a => f(a).value) def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] = - OptionT( - F.flatMap(value){ - case Some(a) => f(a) - case None => F.pure(None) - }) + OptionT(F.flatMap(value)(_.fold(F.pure[Option[B]](None))(f))) def transform[B](f: Option[A] => Option[B])(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(f)) @@ -45,10 +43,7 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { F.map(value)(_.getOrElse(default)) def getOrElseF(default: => F[A])(implicit F: Monad[F]): F[A] = - F.flatMap(value){ - case Some(a) => F.pure(a) - case None => default - } + F.flatMap(value)(_.fold(default)(F.pure)) def collect[B](f: PartialFunction[A, B])(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.collect(f))) @@ -91,6 +86,24 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { XorT(cata(Xor.Right(right), Xor.Left.apply)) def show(implicit F: Show[F[Option[A]]]): String = F.show(value) + + def compare(that: OptionT[F, A])(implicit o: Order[F[Option[A]]]): Int = + o.compare(value, that.value) + + def partialCompare(that: OptionT[F, A])(implicit p: PartialOrder[F[Option[A]]]): Double = + p.partialCompare(value, that.value) + + def ===(that: OptionT[F, A])(implicit eq: Eq[F[Option[A]]]): Boolean = + eq.eqv(value, that.value) + + def traverse[G[_], B](f: A => G[B])(implicit F: Traverse[F], G: Applicative[G]): G[OptionT[F, B]] = + G.map(F.compose(optionInstance).traverse(value)(f))(OptionT.apply) + + def foldLeft[B](b: B)(f: (B, A) => B)(implicit F: Foldable[F]): B = + F.compose(optionInstance).foldLeft(value, b)(f) + + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B])(implicit F: Foldable[F]): Eval[B] = + F.compose(optionInstance).foldRight(value, lb)(f) } object OptionT extends OptionTInstances { @@ -132,12 +145,38 @@ object OptionT extends OptionTInstances { def liftF[F[_], A](fa: F[A])(implicit F: Functor[F]): OptionT[F, A] = OptionT(F.map(fa)(Some(_))) } -private[data] sealed trait OptionTInstances2 { - implicit def catsDataFunctorForOptionT[F[_]:Functor]: Functor[OptionT[F, ?]] = - new Functor[OptionT[F, ?]] { - override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = - fa.map(f) - } +private[data] sealed trait OptionTInstances extends OptionTInstances1 { + implicit def catsDataMonadRecForOptionT[F[_]](implicit F0: MonadRec[F]): MonadRec[OptionT[F, ?]] = + new OptionTMonadRec[F] { implicit val F = F0 } + + implicit def catsDataFoldableForOptionT[F[_]](implicit F0: Foldable[F]): Foldable[OptionT[F, ?]] = + new OptionTFoldable[F] { implicit val F = F0 } + + implicit def catsDataOrderForOptionT[F[_], A](implicit F0: Order[F[Option[A]]]): Order[OptionT[F, A]] = + new OptionTOrder[F, A] { implicit val F = F0 } + + implicit def catsDataShowForOptionT[F[_], A](implicit F: Show[F[Option[A]]]): Show[OptionT[F, A]] = + functor.Contravariant[Show].contramap(F)(_.value) +} + +private[data] sealed trait OptionTInstances1 extends OptionTInstances2 { + /* TODO violates right absorbtion, right distributivity, and left distributivity -- re-enable when MonadCombine laws are split in to weak/strong + implicit def catsDataMonadCombineForOptionT[F[_]](implicit F0: Monad[F]): MonadCombine[OptionT[F, ?]] = + new OptionTMonadCombine[F] { implicit val F = F0 } + */ + + implicit def catsDataTraverseForOptionT[F[_]](implicit F0: Traverse[F]): Traverse[OptionT[F, ?]] = + new OptionTTraverse[F] { implicit val F = F0 } + + implicit def catsDataPartialOrderForOptionT[F[_], A](implicit F0: PartialOrder[F[Option[A]]]): PartialOrder[OptionT[F, A]] = + new OptionTPartialOrder[F, A] { implicit val F = F0 } +} + +private[data] sealed trait OptionTInstances2 extends OptionTInstances3 { + /* TODO violates monadFilter right empty law -- re-enable when MonadFilter laws are split in to weak/strong + implicit def catsDataMonadFilterForOptionT[F[_]](implicit F0: Monad[F]): MonadFilter[OptionT[F, ?]] = + new OptionTMonadFilter[F] { implicit val F = F0 } + */ // do NOT change this to val! I know it looks like it should work, and really I agree, but it doesn't (for... reasons) implicit def catsDataTransLiftForOptionT: TransLift.Aux[OptionT, Functor] = @@ -146,44 +185,112 @@ private[data] sealed trait OptionTInstances2 { def liftT[M[_]: Functor, A](ma: M[A]): OptionT[M, A] = OptionT.liftF(ma) } -} -private[data] sealed trait OptionTInstances1 extends OptionTInstances2 { + implicit def catsDataMonoidKForOptionT[F[_]](implicit F0: Monad[F]): MonoidK[OptionT[F, ?]] = + new OptionTMonoidK[F] { implicit val F = F0 } + + implicit def catsDataEqForOptionT[F[_], A](implicit F0: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = + new OptionTEq[F, A] { implicit val F = F0 } +} +private[data] sealed trait OptionTInstances3 extends OptionTInstances4 { implicit def catsDataMonadForOptionT[F[_]](implicit F0: Monad[F]): Monad[OptionT[F, ?]] = new OptionTMonad[F] { implicit val F = F0 } + + implicit def catsDataMonadErrorForOptionT[F[_], E](implicit F0: MonadError[F, E]): MonadError[OptionT[F, ?], E] = + new OptionTMonadError[F, E] { implicit val F = F0 } + + implicit def catsDataSemigroupK[F[_]](implicit F0: Monad[F]): SemigroupK[OptionT[F, ?]] = + new OptionTSemigroupK[F] { implicit val F = F0 } } -private[data] sealed trait OptionTInstances extends OptionTInstances1 { - implicit def catsDataMonadRecForOptionT[F[_]](implicit F0: MonadRec[F]): MonadRec[OptionT[F, ?]] = - new OptionTMonadRec[F] { implicit val F = F0 } +private[data] sealed trait OptionTInstances4 { + implicit def catsDataFunctorForOptionT[F[_]](implicit F0: Functor[F]): Functor[OptionT[F, ?]] = + new OptionTFunctor[F] { implicit val F = F0 } +} - implicit def catsDataEqForOptionT[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = - FA.on(_.value) +private[data] trait OptionTSemigroupK[F[_]] extends SemigroupK[OptionT[F, ?]] { + implicit def F: Monad[F] - implicit def catsDataShowForOptionT[F[_], A](implicit F: Show[F[Option[A]]]): Show[OptionT[F, A]] = - functor.Contravariant[Show].contramap(F)(_.value) + def combineK[A](x: OptionT[F, A], y: OptionT[F, A]): OptionT[F, A] = x orElse y +} + +private[data] trait OptionTMonoidK[F[_]] extends MonoidK[OptionT[F, ?]] with OptionTSemigroupK[F] { + def empty[A]: OptionT[F, A] = OptionT.none[F, A] +} + +private[data] trait OptionTFunctor[F[_]] extends Functor[OptionT[F, ?]] { + implicit def F: Functor[F] + + override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f) } private[data] trait OptionTMonad[F[_]] extends Monad[OptionT[F, ?]] { - implicit val F: Monad[F] + implicit def F: Monad[F] def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) - def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = - fa.flatMap(f) + def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = fa.flatMap(f) + + override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f) +} - override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = - fa.map(f) +private[data] trait OptionTMonadFilter[F[_]] extends MonadFilter[OptionT[F, ?]] with OptionTMonad[F] { + def empty[A]: OptionT[F, A] = OptionT.none[F, A] } +private[data] trait OptionTMonadCombine[F[_]] extends MonadCombine[OptionT[F, ?]] with OptionTMonad[F] with OptionTMonoidK[F] + private[data] trait OptionTMonadRec[F[_]] extends MonadRec[OptionT[F, ?]] with OptionTMonad[F] { - implicit val F: MonadRec[F] + implicit def F: MonadRec[F] def tailRecM[A, B](a: A)(f: A => OptionT[F, A Xor B]): OptionT[F, B] = - OptionT(F.tailRecM(a)(a0 => F.map(f(a0).value){ - case None => Xor.Right(None) - case Some(Xor.Left(a1)) => Xor.Left(a1) - case Some(Xor.Right(b)) => Xor.Right(Some(b)) - })) + OptionT(F.tailRecM(a)(a0 => F.map(f(a0).value)( + _.fold(Xor.right[A, Option[B]](None))(_.map(Some(_))) + ))) +} + +private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] with OptionTMonad[F] { + override def F: MonadError[F, E] + + override def raiseError[A](e: E): OptionT[F, A] = + OptionT(F.map(F.raiseError[A](e))(Some(_))) + + override def handleErrorWith[A](fa: OptionT[F, A])(f: E => OptionT[F, A]): OptionT[F, A] = + OptionT(F.handleErrorWith(fa.value)(f(_).value)) +} + +private[data] trait OptionTFoldable[F[_]] extends Foldable[OptionT[F, ?]] { + implicit def F: Foldable[F] + + def foldLeft[A, B](fa: OptionT[F, A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: OptionT[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) +} + +private[data] sealed trait OptionTTraverse[F[_]] extends Traverse[OptionT[F, ?]] with OptionTFoldable[F] { + implicit def F: Traverse[F] + + override def traverse[G[_]: Applicative, A, B](fa: OptionT[F, A])(f: A => G[B]): G[OptionT[F, B]] = + fa traverse f +} + +private[data] sealed trait OptionTEq[F[_], A] extends Eq[OptionT[F, A]] { + implicit def F: Eq[F[Option[A]]] + + override def eqv(x: OptionT[F, A], y: OptionT[F, A]): Boolean = x === y +} + +private[data] sealed trait OptionTPartialOrder[F[_], A] extends PartialOrder[OptionT[F, A]] with OptionTEq[F, A]{ + override implicit def F: PartialOrder[F[Option[A]]] + + override def partialCompare(x: OptionT[F, A], y: OptionT[F, A]): Double = x partialCompare y +} + +private[data] sealed trait OptionTOrder[F[_], A] extends Order[OptionT[F, A]] with OptionTPartialOrder[F, A]{ + override implicit def F: Order[F[Option[A]]] + + override def compare(x: OptionT[F, A], y: OptionT[F, A]): Int = x compare y } diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 1536f256030..469e55d28e8 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -44,14 +44,21 @@ object ListWrapper { def eqv[A : Eq]: Eq[ListWrapper[A]] = Eq[List[A]].on[ListWrapper[A]](_.list) - val foldable: Foldable[ListWrapper] = - new Foldable[ListWrapper] { - def foldLeft[A, B](fa: ListWrapper[A], b: B)(f: (B, A) => B): B = - Foldable[List].foldLeft(fa.list, b)(f) + val traverse: Traverse[ListWrapper] = { + val F = Traverse[List] + new Traverse[ListWrapper] { + def foldLeft[A, B](fa: ListWrapper[A], b: B)(f: (B, A) => B): B = + F.foldLeft(fa.list, b)(f) def foldRight[A, B](fa: ListWrapper[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Foldable[List].foldRight(fa.list, lb)(f) + F.foldRight(fa.list, lb)(f) + def traverse[G[_], A, B](fa: ListWrapper[A])(f: A => G[B])(implicit G0: Applicative[G]): G[ListWrapper[B]] = { + G0.map(F.traverse(fa.list)(f))(ListWrapper.apply) + } } + } + + val foldable: Foldable[ListWrapper] = traverse val functor: Functor[ListWrapper] = new Functor[ListWrapper] { @@ -85,6 +92,19 @@ object ListWrapper { } } + val monadRec: MonadRec[ListWrapper] = { + val M = MonadRec[List] + + new MonadRec[ListWrapper] { + def pure[A](x: A): ListWrapper[A] = ListWrapper(M.pure(x)) + def flatMap[A, B](fa: ListWrapper[A])(f: A => ListWrapper[B]): ListWrapper[B] = ListWrapper(M.flatMap(fa.list)(a => f(a).list)) + def tailRecM[A, B](a: A)(f: A => ListWrapper[cats.data.Xor[A,B]]): ListWrapper[B] = + ListWrapper(M.tailRecM(a)(a => f(a).list)) + } + } + + val flatMapRec: FlatMapRec[ListWrapper] = monadRec + val monad: Monad[ListWrapper] = monadCombine val applicative: Applicative[ListWrapper] = monadCombine diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 9d12864e889..6cab95418a9 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,11 +1,124 @@ -package cats.tests +package cats +package tests -import cats.{Id, MonadRec, Cartesian, Show} -import cats.data.{OptionT, Xor} -import cats.laws.discipline.{FunctorTests, SerializableTests, CartesianTests, MonadRecTests} +import cats.data.{OptionT, Xor, XorT} +import cats.kernel.laws.OrderLaws +import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ class OptionTTests extends CatsSuite { + implicit val iso = CartesianTests.Isomorphisms.invariant[OptionT[ListWrapper, ?]](OptionT.catsDataFunctorForOptionT(ListWrapper.functor)) + + { + implicit val F = ListWrapper.order[Option[Int]] + + checkAll("OptionT[ListWrapper, Int]", OrderLaws[OptionT[ListWrapper, Int]].order) + checkAll("Order[OptionT[ListWrapper, Int]]", SerializableTests.serializable(Order[OptionT[ListWrapper, Int]])) + + PartialOrder[OptionT[ListWrapper, Int]] + Eq[OptionT[ListWrapper, Int]] + } + + { + // F has a Functor + implicit val F = ListWrapper.functor + + checkAll("OptionT[ListWrapper, Int]", FunctorTests[OptionT[ListWrapper, ?]].functor[Int, Int, Int]) + checkAll("Functor[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Functor[OptionT[ListWrapper, ?]])) + } + + { + // F has a Monad + implicit val F = ListWrapper.monad + implicit val eq0 = OptionT.catsDataEqForOptionT[ListWrapper, Option[Int]] + implicit val eq1 = OptionT.catsDataEqForOptionT[OptionT[ListWrapper, ?], Int](eq0) + + checkAll("OptionT[ListWrapper, Int]", MonadTests[OptionT[ListWrapper, ?]].monad[Int, Int, Int]) + checkAll("Monad[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Monad[OptionT[ListWrapper, ?]])) + + // checkAll("OptionT[ListWrapper, Int]", MonadFilterTests[OptionT[ListWrapper, ?]].monadFilter[Int, Int, Int]) + // checkAll("MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonadFilter[OptionT[ListWrapper, ?]])) + + // checkAll("OptionT[ListWrapper, Int]", MonadCombineTests[OptionT[ListWrapper, ?]].monadCombine[Int, Int, Int]) + // checkAll("MonadCombine[OptionT[ListWrapper, ?]]", SerializableTests.serializable(MonadCombine[OptionT[ListWrapper, ?]])) + + checkAll("OptionT[ListWrapper, Int]", SemigroupKTests[OptionT[ListWrapper, ?]].semigroupK[Int]) + checkAll("SemigroupK[OptionT[ListWrapper, ?]]", SerializableTests.serializable(SemigroupK[OptionT[ListWrapper, ?]])) + + checkAll("OptionT[ListWrapper, Int]", MonoidKTests[OptionT[ListWrapper, ?]].monoidK[Int]) + checkAll("MonoidK[OptionT[ListWrapper, ?]]", SerializableTests.serializable(MonoidK[OptionT[ListWrapper, ?]])) + + FlatMap[OptionT[ListWrapper, ?]] + // Alternative[OptionT[ListWrapper, ?]] + Applicative[OptionT[ListWrapper, ?]] + Apply[OptionT[ListWrapper, ?]] + Functor[OptionT[ListWrapper, ?]] + MonoidK[OptionT[ListWrapper, ?]] + SemigroupK[OptionT[ListWrapper, ?]] + } + + { + // F has a MonadRec + implicit val F = ListWrapper.monadRec + + checkAll("OptionT[ListWrapper, Int]", MonadRecTests[OptionT[ListWrapper, ?]].monadRec[Int, Int, Int]) + checkAll("MonadRec[OptionT[ListWrapper, ?]]", SerializableTests.serializable(MonadRec[OptionT[ListWrapper, ?]])) + + Monad[OptionT[ListWrapper, ?]] + FlatMap[OptionT[ListWrapper, ?]] + Applicative[OptionT[ListWrapper, ?]] + Apply[OptionT[ListWrapper, ?]] + Functor[OptionT[ListWrapper, ?]] + } + + { + // F has a MonadError + type SXor[A] = String Xor A + + implicit val monadError = OptionT.catsDataMonadErrorForOptionT[SXor, String] + + import org.scalacheck.Arbitrary + implicit val arb1 = implicitly[Arbitrary[OptionT[SXor, Int]]] + implicit val arb2 = implicitly[Arbitrary[OptionT[SXor, Int => Int]]] + + implicit val eq0 = OptionT.catsDataEqForOptionT[SXor, Option[Int]] + implicit val eq1 = OptionT.catsDataEqForOptionT[SXor, Int] + implicit val eq2 = OptionT.catsDataEqForOptionT[SXor, Unit] + implicit val eq3 = OptionT.catsDataEqForOptionT[SXor, SXor[Unit]] + implicit val eq4 = OptionT.catsDataEqForOptionT[SXor, SXor[Int]] + implicit val eq5 = XorT.catsDataEqForXorT[OptionT[SXor, ?], String, Int] + implicit val eq6 = OptionT.catsDataEqForOptionT[SXor, (Int, Int, Int)] + + implicit val iso = CartesianTests.Isomorphisms.invariant[OptionT[SXor, ?]] + + checkAll("OptionT[String Xor ?, Int]", MonadErrorTests[OptionT[SXor, ?], String].monadError[Int, Int, Int]) + checkAll("MonadError[OptionT[String Xor ?, ?]]", SerializableTests.serializable(monadError)) + + Monad[OptionT[SXor, ?]] + FlatMap[OptionT[SXor, ?]] + Applicative[OptionT[SXor, ?]] + Apply[OptionT[SXor, ?]] + Functor[OptionT[SXor, ?]] + } + + { + // F has a Foldable + implicit val F = ListWrapper.foldable + + checkAll("OptionT[ListWrapper, Int]", FoldableTests[OptionT[ListWrapper, ?]].foldable[Int, Int]) + checkAll("Foldable[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Foldable[OptionT[ListWrapper, ?]])) + } + + { + // F has a Traverse + implicit val F = ListWrapper.traverse + + checkAll("OptionT[ListWrapper, Int] with Option", TraverseTests[OptionT[ListWrapper, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Traverse[OptionT[ListWrapper, ?]])) + + Foldable[OptionT[ListWrapper, ?]] + Functor[OptionT[ListWrapper, ?]] + } test("fold and cata consistent") { forAll { (o: OptionT[List, Int], s: String, f: Int => String) => @@ -122,11 +235,6 @@ class OptionTTests extends CatsSuite { } } - implicit val iso = CartesianTests.Isomorphisms.invariant[OptionT[List, ?]] - - checkAll("OptionT[List, Int]", CartesianTests[OptionT[List, ?]].cartesian[Int, Int, Int]) - checkAll("Cartesian[OptionT[List, ?]]", SerializableTests.serializable(Cartesian[OptionT[List, ?]])) - test("liftF") { forAll { (xs: List[Int]) => xs.map(Option(_)) should ===(OptionT.liftF(xs).value) @@ -139,7 +247,7 @@ class OptionTTests extends CatsSuite { } test("none") { - OptionT.none[List,Int] should === (OptionT[List,Int](List(None))) + OptionT.none[List, Int] should === (OptionT[List, Int](List(None))) } test("implicit Show[OptionT] instance and explicit show method are consistent") { @@ -160,11 +268,4 @@ class OptionTTests extends CatsSuite { } } - checkAll("OptionT[List, Int]", MonadRecTests[OptionT[List, ?]].monadRec[Int, Int, Int]) - checkAll("MonadRec[OptionT[List, ?]]", SerializableTests.serializable(MonadRec[OptionT[List, ?]])) - - { - implicit val F = ListWrapper.functor - checkAll("Functor[OptionT[ListWrapper, ?]]", FunctorTests[OptionT[ListWrapper, ?]].functor[Int, Int, Int]) - } }