From 4df64bde91c0d216aa90aadcc41c76531e9bf3a7 Mon Sep 17 00:00:00 2001 From: Jun Tomioka Date: Fri, 28 Jun 2019 02:06:22 +0900 Subject: [PATCH] Add listen for Writer and WriterT (#2916) --- core/src/main/scala/cats/data/WriterT.scala | 13 ++++++++++++- core/src/main/scala/cats/data/package.scala | 3 +++ .../test/scala/cats/tests/WriterTSuite.scala | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index f25a6522d5..01f3013261 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -16,6 +16,11 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { def value(implicit functorF: Functor[F]): F[V] = functorF.map(run)(_._2) + def listen(implicit F: Functor[F]): WriterT[F, L, (V, L)] = + WriterT(F.map(run) { + case (l, v) => (l, (v, l)) + }) + def ap[Z](f: WriterT[F, L, V => Z])(implicit F: Apply[F], L: Semigroup[L]): WriterT[F, L, Z] = WriterT(F.map2(f.run, run) { case ((l1, fvz), (l2, v)) => (L.combine(l1, l2), fvz(v)) @@ -84,7 +89,7 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { )(WriterT.apply) } -object WriterT extends WriterTInstances with WriterTFunctions { +object WriterT extends WriterTInstances with WriterTFunctions with WriterTFunctions0 { def liftF[F[_], L, V](fv: F[V])(implicit monoidL: Monoid[L], F: Applicative[F]): WriterT[F, L, V] = WriterT(F.map(fv)(v => (monoidL.empty, v))) @@ -527,6 +532,12 @@ sealed private[data] trait WriterTComonad[F[_], L] extends Comonad[WriterT[F, L, def extract[A](fa: WriterT[F, L, A]): A = F0.extract(F0.map(fa.run)(_._2)) } +// new trait for binary compatibility +private[data] trait WriterTFunctions0 { + def listen[F[_], L, V](writerTFLV: WriterT[F, L, V])(implicit functorF: Functor[F]): WriterT[F, L, (V, L)] = + writerTFLV.listen +} + private[data] trait WriterTFunctions { def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = WriterT(functorF.map(vf)(v => (l, v))) diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index d6b4e6b493..1621bcd6bf 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -48,6 +48,9 @@ package object data { def value[L: Monoid, V](v: V): Writer[L, V] = WriterT.value(v) def tell[L](l: L): Writer[L, Unit] = WriterT.tell(l) + + def listen[L, V](writer: Writer[L, V]): Writer[L, (V, L)] = + WriterT.listen(writer) } type IndexedState[S1, S2, A] = IndexedStateT[Eval, S1, S2, A] diff --git a/tests/src/test/scala/cats/tests/WriterTSuite.scala b/tests/src/test/scala/cats/tests/WriterTSuite.scala index 19b77528cc..b3fdf1019e 100644 --- a/tests/src/test/scala/cats/tests/WriterTSuite.scala +++ b/tests/src/test/scala/cats/tests/WriterTSuite.scala @@ -67,6 +67,18 @@ class WriterTSuite extends CatsSuite { } } + test("value + listen + map(_._1) + value is identity") { + forAll { (i: Int) => + WriterT.value[Id, Int, Int](i).listen.map(_._1).value should ===(i) + } + } + + test("tell + listen + map(_._2) + value is identity") { + forAll { (i: Int) => + WriterT.tell[Id, Int](i).listen.map(_._2).value should ===(i) + } + } + test("Writer.pure and WriterT.liftF are consistent") { forAll { (i: Int) => val writer: Writer[String, Int] = Writer.value(i) @@ -91,6 +103,11 @@ class WriterTSuite extends CatsSuite { Writer.tell("foo").written should ===("foo") } + test("listen returns a tuple of value and log") { + val w: Writer[String, Int] = Writer("foo", 3) + w.listen should ===(Writer("foo", (3, "foo"))) + } + test("mapK consistent with f(value)+pure") { val f: List ~> Option = λ[List ~> Option](_.headOption) forAll { (writert: WriterT[List, String, Int]) =>