From 23e0a7283189668addba794bf98adf86640dbf5a Mon Sep 17 00:00:00 2001 From: Filippo Mariotti Date: Mon, 12 Mar 2018 16:28:27 +0000 Subject: [PATCH] Add `Foldable`, `Traverse` and `Comonad` instances to `WriterT` (#2182) * Added foldLeft and foldRight to WriterT and Foldable instance * Added tests for Foldable instance * Added traverse. Fixed priority of the implicit search for isntances * Added traverse implementation to WriterT * Added test for WriterT Traverse * Refactored names * Changed priority of Traverse instance * Added comonad instance for WrtiterT and Const. Added tests * Completed Comonad tests using tuple instead of Const * Refactored extract derivation to avoid implicit search * Added instances and reolution tests for Id. Removed unneeded type parameters. * Minor refactoring * Attempt to increase coverage * Removed unneeded serialization test * Refactored traverse derivation * Fixed improper inheritance of WriterTFunctor --- core/src/main/scala/cats/data/WriterT.scala | 86 ++++++++++++++++--- .../test/scala/cats/tests/WriterTSuite.scala | 48 +++++++++++ 2 files changed, 121 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 95620f4cac..1f09eb8723 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -1,8 +1,8 @@ package cats package data +import cats.Foldable import cats.kernel.instances.tuple._ - import cats.kernel.CommutativeMonoid import cats.syntax.semigroup._ @@ -63,6 +63,17 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { mapWritten(_ => monoidL.empty) def show(implicit F: Show[F[(L, V)]]): String = F.show(run) + + def foldLeft[C](c: C)(f: (C, V) => C)(implicit F: Foldable[F]): C = + F.foldLeft(run, c)((z, lv) => f(z, lv._2)) + + def foldRight[C](lc: Eval[C])(f: (V, Eval[C]) => Eval[C])(implicit F: Foldable[F]): Eval[C] = + F.foldRight(run, lc)((lv, z) => f(lv._2, z)) + + def traverse[G[_], V1](f: V => G[V1])(implicit F: Traverse[F], G: Applicative[G]): G[WriterT[F, L, V1]] = + G.map( + F.traverse(run)(lv => G.tupleLeft(f(lv._2), lv._1)) + )(WriterT.apply) } object WriterT extends WriterTInstances with WriterTFunctions { @@ -95,9 +106,23 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { implicit val F0: Monad[F] = F implicit val L0: Monoid[L] = L } + + implicit def catsDataTraverseForWriterTId[L](implicit F: Traverse[Id]): Traverse[WriterT[Id, L, ?]] = + catsDataTraverseForWriterT[Id, L](F) } private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { + + implicit def catsDataTraverseForWriterT[F[_], L](implicit F: Traverse[F]): Traverse[WriterT[F, L, ?]] = + new WriterTTraverse[F, L] { + val F0: Traverse[F] = F + } + + implicit def catsDataFoldableForWriterTId[L](implicit F: Foldable[Id]): Foldable[WriterT[Id, L, ?]] = + catsDataFoldableForWriterT[Id, L](F) +} + +private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { implicit def catsDataMonadErrorForWriterT[F[_], L, E](implicit F: MonadError[F, E], L: Monoid[L]): MonadError[WriterT[F, L, ?], E] = new WriterTMonadError[F, L, E] { implicit val F0: MonadError[F, E] = F @@ -134,9 +159,14 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 implicit def catsDataMonoidForWriterTId[L:Monoid, V:Monoid]: Monoid[WriterT[Id, L, V]] = catsDataMonoidForWriterT[Id, L, V] + + implicit def catsDataFoldableForWriterT[F[_], L](implicit F: Foldable[F]): Foldable[WriterT[F, L, ?]] = + new WriterTFoldable[F, L]{ + val F0: Foldable[F] = F + } } -private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { +private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { implicit def catsDataMonadForWriterTId[L:Monoid]: Monad[WriterT[Id, L, ?]] = catsDataMonadForWriterT[Id, L] @@ -145,9 +175,12 @@ private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 implicit def catsDataSemigroupForWriterTId[L:Semigroup, V:Semigroup]: Semigroup[WriterT[Id, L, V]] = catsDataSemigroupForWriterT[Id, L, V] + + implicit def catsDataComonadForWriterTId[L](implicit F: Comonad[Id]): Comonad[WriterT[Id, L, ?]] = + catsDataComonadForWriterT[Id, L](F) } -private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { +private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { implicit def catsDataMonadForWriterT[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = new WriterTMonad[F, L] { implicit val F0: Monad[F] = F @@ -163,12 +196,12 @@ private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 catsDataCoflatMapForWriterT[Id, L] } -private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { +private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 { implicit def catsDataFlatMapForWriterTId[L:Semigroup]: FlatMap[WriterT[Id, L, ?]] = catsDataFlatMapForWriterT2[Id, L] } -private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 { +private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 { implicit def catsDataFlatMapForWriterT1[F[_], L](implicit F: FlatMap[F], L: Monoid[L]): FlatMap[WriterT[F, L, ?]] = new WriterTFlatMap1[F, L] { implicit val F0: FlatMap[F] = F @@ -181,7 +214,7 @@ private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 } } -private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 { +private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 { implicit def catsDataApplicativeErrorForWriterT[F[_], L, E](implicit F: ApplicativeError[F, E], L: Monoid[L]): ApplicativeError[WriterT[F, L, ?], E] = new WriterTApplicativeError[F, L, E] { implicit val F0: ApplicativeError[F, E] = F @@ -189,7 +222,7 @@ private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 } } -private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 { +private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 { implicit def catsDataAlternativeForWriterT[F[_], L](implicit F: Alternative[F], L: Monoid[L]): Alternative[WriterT[F, L, ?]] = new WriterTAlternative[F, L] { implicit val F0: Alternative[F] = F @@ -202,7 +235,7 @@ private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 } } -private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 { +private[data] sealed abstract class WriterTInstances8 extends WriterTInstances9 { implicit def catsDataMonoidKForWriterT[F[_], L](implicit F: MonoidK[F]): MonoidK[WriterT[F, L, ?]] = new WriterTMonoidK[F, L] { implicit val F0: MonoidK[F] = F @@ -219,7 +252,7 @@ private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 } } -private[data] sealed abstract class WriterTInstances8 extends WriterTInstances9 { +private[data] sealed abstract class WriterTInstances9 extends WriterTInstances10 { implicit def catsDataSemigroupKForWriterT[F[_], L](implicit F: SemigroupK[F]): SemigroupK[WriterT[F, L, ?]] = new WriterTSemigroupK[F, L] { implicit val F0: SemigroupK[F] = F @@ -230,10 +263,9 @@ private[data] sealed abstract class WriterTInstances8 extends WriterTInstances9 implicit val F0: Applicative[F] = F implicit val L0: Monoid[L] = L } - } -private[data] sealed abstract class WriterTInstances9 extends WriterTInstances10 { +private[data] sealed abstract class WriterTInstances10 extends WriterTInstances11 { implicit def catsDataApplyForWriterT[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] = new WriterTApply[F, L] { implicit val F0: Apply[F] = F @@ -241,7 +273,14 @@ private[data] sealed abstract class WriterTInstances9 extends WriterTInstances10 } } -private[data] sealed abstract class WriterTInstances10 { +private[data] sealed abstract class WriterTInstances11 extends WriterTInstances12 { + implicit def catsDataComonadForWriterT[F[_], L](implicit F: Comonad[F]): Comonad[WriterT[F, L, ?]] = + new WriterTComonad[F, L] { + implicit val F0: Comonad[F] = F + } +} + +private[data] sealed abstract class WriterTInstances12 { implicit def catsDataCoflatMapForWriterT[F[_], L](implicit F: Functor[F]): CoflatMap[WriterT[F, L, ?]] = new WriterTCoflatMap[F, L] { implicit val F0: Functor[F] = F @@ -251,7 +290,7 @@ private[data] sealed abstract class WriterTInstances10 { private[data] sealed trait WriterTFunctor[F[_], L] extends Functor[WriterT[F, L, ?]] { implicit def F0: Functor[F] - def map[A, B](fa: WriterT[F, L, A])(f: A => B): WriterT[F, L, B] = + override def map[A, B](fa: WriterT[F, L, A])(f: A => B): WriterT[F, L, B] = fa.map(f) } @@ -410,6 +449,27 @@ private[data] sealed trait WriterTCoflatMap[F[_], L] extends CoflatMap[WriterT[F def coflatMap[A, B](fa: WriterT[F, L, A])(f: WriterT[F, L, A] => B): WriterT[F, L, B] = fa.map(_ => f(fa)) } +private[data] sealed trait WriterTFoldable[F[_], L] extends Foldable[WriterT[F, L, ?]] { + + implicit def F0: Foldable[F] + + def foldLeft[A, B](fa: WriterT[F, L, A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) + def foldRight[A, B](fa: WriterT[F, L, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(lb)(f) +} + +private[data] sealed trait WriterTTraverse[F[_], L] extends Traverse[WriterT[F, L, ?]] with WriterTFoldable[F, L] with WriterTFunctor[F, L] { + + override implicit def F0: Traverse[F] + + def traverse[G[_]: Applicative, A, B](fa: WriterT[F, L, A])(f: A => G[B]): G[WriterT[F, L, B]] = fa.traverse(f) +} + +private[data] sealed trait WriterTComonad[F[_], L] extends Comonad[WriterT[F, L, ?]] with WriterTCoflatMap[F, L] { + + override implicit def F0: Comonad[F] + + def extract[A](fa: WriterT[F, L, A]): A = F0.extract(F0.map(fa.run)(_._2)) +} private[data] trait WriterTFunctions { def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = diff --git a/tests/src/test/scala/cats/tests/WriterTSuite.scala b/tests/src/test/scala/cats/tests/WriterTSuite.scala index 255da35c5a..19b98868ab 100644 --- a/tests/src/test/scala/cats/tests/WriterTSuite.scala +++ b/tests/src/test/scala/cats/tests/WriterTSuite.scala @@ -370,6 +370,54 @@ class WriterTSuite extends CatsSuite { checkAll("ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]]", SerializableTests.serializable(ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]])) } + { + // F has a Foldable and L has a Monoid + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + Foldable[Const[String, ?]] + Foldable[WriterT[Const[String, ?], ListWrapper[Int], ?]] + + checkAll("WriterT[Const[String, ?], ListWrapper[Int], ?]", FoldableTests[WriterT[Const[String, ?], ListWrapper[Int], ?]].foldable[Int, Int]) + checkAll("Foldable[WriterT[Const[String, ?], ListWrapper[Int], ?]]", SerializableTests.serializable(Foldable[WriterT[Const[String, ?], ListWrapper[Int], ?]])) + + Foldable[Id] + Foldable[WriterT[Id, ListWrapper[Int], ?]] + Foldable[Writer[ListWrapper[Int], ?]] + + checkAll("WriterT[Id, ListWrapper[Int], ?]", FoldableTests[WriterT[Id, ListWrapper[Int], ?]].foldable[Int, Int]) + } + + { + // F has a Traverse and L has a Monoid + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + Traverse[Const[String, ?]] + Traverse[WriterT[Const[String, ?], ListWrapper[Int], ?]] + + checkAll("WriterT[Const[String, ?], ListWrapper[Int], ?]", TraverseTests[WriterT[Const[String, ?], ListWrapper[Int], ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[WriterT[Const[String, ?], ListWrapper[Int], ?]]", SerializableTests.serializable(Traverse[WriterT[Const[String, ?], ListWrapper[Int], ?]])) + + Traverse[Id] + Traverse[WriterT[Id, ListWrapper[Int], ?]] + Traverse[Writer[ListWrapper[Int], ?]] + + checkAll("WriterT[Id, ListWrapper[Int], ?]", TraverseTests[WriterT[Id, ListWrapper[Int], ?]].traverse[Int, Int, Int, Int, Option, Option]) + } + + { + // F has a Comonad and L has a Monoid + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + Comonad[(String, ?)] + Comonad[WriterT[(String, ?), ListWrapper[Int], ?]] + + checkAll("WriterT[(String, ?), ListWrapper[Int], ?]", ComonadTests[WriterT[(String, ?), ListWrapper[Int], ?]].comonad[Int, Int, Int]) + checkAll("Comonad[WriterT[(String, ?), ListWrapper[Int], ?]]", SerializableTests.serializable(Comonad[WriterT[(String, ?), ListWrapper[Int], ?]])) + + Comonad[Id] + Comonad[WriterT[Id, ListWrapper[Int], ?]] + Comonad[Writer[ListWrapper[Int], ?]] + + checkAll("WriterT[Id, ListWrapper[Int], ?]", ComonadTests[WriterT[Id, ListWrapper[Int], ?]].comonad[Int, Int, Int]) + } + checkAll("WriterT[Option, Int, ?]", CommutativeMonadTests[WriterT[Option, Int, ?]].commutativeMonad[Int, Int, Int]) checkAll("CommutativeMonad[WriterT[Option, Int, ?]]",SerializableTests.serializable(CommutativeMonad[WriterT[Option, Int, ?]])) }