diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 515d63f5a5..5cf55f8fd0 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -44,6 +44,8 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { def reset(implicit monoidL: Monoid[L], functorF: Functor[F]): WriterT[F, L, V] = mapWritten(_ => monoidL.empty) + + def show(implicit F: Show[F[(L, V)]]): String = F.show(run) } object WriterT extends WriterTInstances with WriterTFunctions @@ -68,6 +70,10 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { def liftT[A](ma: M[A]): WriterT[M, W, A] = WriterT(M.map(ma)((W.empty, _))) } + + implicit def writerTShow[F[_], L, V](implicit F: Show[F[(L, V)]]): Show[WriterT[F, L, V]] = new Show[WriterT[F, L, V]] { + override def show(f: WriterT[F, L, V]): String = f.show + } } private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { diff --git a/core/src/main/scala/cats/std/tuple.scala b/core/src/main/scala/cats/std/tuple.scala index cbf9f8d075..a690245a81 100644 --- a/core/src/main/scala/cats/std/tuple.scala +++ b/core/src/main/scala/cats/std/tuple.scala @@ -15,4 +15,10 @@ sealed trait Tuple2Instances { def bifoldRight[A, B, C](fab: (A, B), c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = g(fab._2, f(fab._1, c)) } + + implicit def tuple2Show[A, B](implicit aShow: Show[A], bShow: Show[B]): Show[(A, B)] = new Show[(A, B)] { + override def show(f: (A, B)): String = { + s"(${aShow.show(f._1)},${bShow.show(f._2)})" + } + } } diff --git a/tests/src/test/scala/cats/tests/TupleTests.scala b/tests/src/test/scala/cats/tests/TupleTests.scala index 419fabc698..dae6c08429 100644 --- a/tests/src/test/scala/cats/tests/TupleTests.scala +++ b/tests/src/test/scala/cats/tests/TupleTests.scala @@ -7,4 +7,27 @@ import cats.laws.discipline.eq.tuple2Eq class TupleTests extends CatsSuite { checkAll("Tuple2", BitraverseTests[Tuple2].bitraverse[Option, Int, Int, Int, String, String, String]) checkAll("Bitraverse[Tuple2]", SerializableTests.serializable(Bitraverse[Tuple2])) + + test("show") { + (1, 2).show should === ("(1,2)") + + forAll { fs: (String, String) => + fs.show should === (fs.toString) + } + + // Provide some "non-standard" Show instances to make sure the tuple2 is actually use the Show instances for the + // relevant types instead of blindly calling toString + case class Foo(x: Int) + implicit val fooShow: Show[Foo] = new Show[Foo] { + override def show(f: Foo): String = s"foo.x = ${f.x}" + } + case class Bar(y: Int) + implicit val barShow: Show[Bar] = new Show[Bar] { + override def show(f: Bar): String = s"bar.y = ${f.y}" + } + + val foo = Foo(1) + val bar = Bar(2) + (foo, bar).show should === (s"(${fooShow.show(foo)},${barShow.show(bar)})") + } } diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 7d8e874f9b..a106e05ae3 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -62,6 +62,11 @@ class WriterTTests extends CatsSuite { } } + test("show") { + val writerT: WriterT[Id, List[String], String] = WriterT.put("foo")(List("Some log message")) + writerT.show should === ("(List(Some log message),foo)") + } + { // F has a SemigroupK implicit val F: SemigroupK[ListWrapper] = ListWrapper.semigroupK