From 1741ce533b5ec09a5f89ad6c837d17ed38caf9b4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 12 Nov 2015 08:59:10 -0500 Subject: [PATCH 01/14] Add WriterT instances for supertypes of Monad --- core/src/main/scala/cats/data/WriterT.scala | 103 ++++++++++++- .../cats/laws/discipline/Arbitrary.scala | 11 +- .../test/scala/cats/tests/WriterTTests.scala | 137 +++++++++++++++++- 3 files changed, 235 insertions(+), 16 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 09e65ce48f..247425ab9b 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -8,6 +8,12 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { def value(implicit functorF: Functor[F]): F[V] = functorF.map(run)(_._2) + 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)) + }) + def map[Z](fn: V => Z)(implicit functorF: Functor[F]): WriterT[F, L, Z] = WriterT { functorF.map(run) { z => (z._1, fn(z._2)) } @@ -36,21 +42,102 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { } object WriterT extends WriterTInstances with WriterTFunctions -private[data] sealed abstract class WriterTInstances { - implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]): Monad[WriterT[F, L, ?]] = { - new Monad[WriterT[F, L, ?]] { - override def pure[A](a: A): WriterT[F, L, A] = - WriterT.value[F, L, A](a) +private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { + + implicit def writerTIdMonad[L:Monoid]: Monad[WriterT[Id, L, ?]] = + writerTMonad[Id, L] - override def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = - fa.flatMap(a => f(a)) + // The Eq[(L, V)] can be derived from an Eq[L] and Eq[V], but we are waiting + // on an algebra release that includes https://github.com/non/algebra/pull/82 + implicit def writerTIdEq[L, V](implicit E: Eq[(L, V)]): Eq[WriterT[Id, L, V]] = + writerTEq[Id, L, V] +} + +private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { + implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = + new WriterTMonad[F, L] { + implicit val F0: Monad[F] = F + implicit val L0: Monoid[L] = L } - } + + implicit def writerTIdFunctor[L]: Functor[WriterT[Id, L, ?]] = + writerTFunctor[Id, L] + + implicit def writerTIdFlatMap[L:Semigroup]: FlatMap[WriterT[Id, L, ?]] = + writerTFlatMap[Id, L] implicit def writerTEq[F[_], L, V](implicit F: Eq[F[(L, V)]]): Eq[WriterT[F, L, V]] = F.on(_.run) } +private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { + implicit def writerTApplicative[F[_], L](implicit F: Applicative[F], L: Monoid[L]): Applicative[WriterT[F, L, ?]] = + new WriterTApplicative[F, L] { + implicit val F0: Applicative[F] = F + implicit val L0: Monoid[L] = L + } +} +private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { + implicit def writerTFlatMap[F[_], L](implicit F: FlatMap[F], L: Semigroup[L]): FlatMap[WriterT[F, L, ?]] = + new WriterTFlatMap[F, L] { + implicit val F0: FlatMap[F] = F + implicit val L0: Semigroup[L] = L + } +} + +private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { + implicit def writerTApply[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] = + new WriterTApply[F, L] { + implicit val F0: Apply[F] = F + implicit val L0: Semigroup[L] = L + } +} + +private[data] sealed abstract class WriterTInstances4 { + implicit def writerTFunctor[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] = new WriterTFunctor[F, L] { + implicit val F0: Functor[F] = F + } +} + +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] = + fa.map(f) +} + +private[data] sealed trait WriterTApply[F[_], L] extends WriterTFunctor[F, L] with Apply[WriterT[F, L, ?]] { + override implicit def F0: Apply[F] + implicit def L0: Semigroup[L] + + def ap[A, B](fa: WriterT[F, L, A])(f: WriterT[F, L, A => B]): WriterT[F, L, B] = + fa ap f +} + +private[data] sealed trait WriterTFlatMap[F[_], L] extends WriterTApply[F, L] with FlatMap[WriterT[F, L, ?]] { + override implicit def F0: FlatMap[F] + implicit def L0: Semigroup[L] + + def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = + fa flatMap f +} + +private[data] sealed trait WriterTApplicative[F[_], L] extends WriterTApply[F, L] with Applicative[WriterT[F, L, ?]] { + override implicit def F0: Applicative[F] + override implicit def L0: Monoid[L] + + def pure[A](a: A): WriterT[F, L, A] = + WriterT.value[F, L, A](a) +} + +private[data] sealed trait WriterTMonad[F[_], L] extends WriterTApplicative[F, L] with Monad[WriterT[F, L, ?]] { + override implicit def F0: Monad[F] + override implicit def L0: Monoid[L] + + def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = + fa.flatMap(f) +} + 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/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7169c22a51..974c2ef288 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -9,7 +9,7 @@ import org.scalacheck.Arbitrary.{arbitrary => getArbitrary} /** * Arbitrary instances for cats.data */ -object arbitrary { +object arbitrary extends ArbitraryInstances0 { implicit def constArbitrary[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] = Arbitrary(A.arbitrary.map(Const[A, B])) @@ -76,10 +76,15 @@ object arbitrary { as <- Gen.listOf(A.arbitrary).map(_.take(8)) } yield StreamingT.fromList(as)) - implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = - Arbitrary(F.arbitrary.map(WriterT(_))) + implicit def writerArbitrary[L:Arbitrary, V:Arbitrary]: Arbitrary[Writer[L, V]] = + writerTArbitrary[Id, L, V] // until this is provided by scalacheck implicit def partialFunctionArbitrary[A, B](implicit F: Arbitrary[A => Option[B]]): Arbitrary[PartialFunction[A, B]] = Arbitrary(F.arbitrary.map(Function.unlift)) } + +private[discipline] sealed trait ArbitraryInstances0 { + implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = + Arbitrary(F.arbitrary.map(WriterT(_))) +} diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index deb5b9a67b..a3cb0bd18c 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -1,33 +1,160 @@ package cats package tests -import cats.data.WriterT +import cats.data.{Writer, WriterT} import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ +import algebra.laws.OrderLaws import org.scalacheck.Prop.forAll class WriterTTests extends CatsSuite { - checkAll("WriterT[List, String, Int]", MonadTests[WriterT[List, String, ?]].monad[String, Int, Int]) + type Logged[A] = Writer[ListWrapper[Int], A] + + // we have a lot of generated lists of lists in these tests. We have to tell + // Scalacheck to calm down a bit so we don't hit memory and test duration + // issues. + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = 5, minSuccessful = 20) + + checkAll("WriterT[List, Int, Int]", OrderLaws[WriterT[List, Int, Int]].eqv) + checkAll("Eq[WriterT[List, Int, Int]]", SerializableTests.serializable(Eq[WriterT[List, Int, Int]])) + // check that this resolves + Eq[Writer[Int, Int]] test("double swap is a noop"){ - forAll { w: WriterT[List, String, Int] => + forAll { w: WriterT[List, Int, Int] => w.swap.swap should === (w) } } test("reset on pure is a noop"){ forAll { i: Int => - val w = Monad[WriterT[List, String, ?]].pure(i) + val w = Monad[WriterT[List, Int, ?]].pure(i) w should === (w.reset) } } test("reset consistencey"){ - forAll { (i: Int, w1: WriterT[Id, String, Int], w2: WriterT[Id, String, Int]) => + forAll { (i: Int, w1: WriterT[Id, Int, Int], w2: WriterT[Id, Int, Int]) => // if the value is the same, everything should be the same w1.map(_ => i).reset should === (w2.map(_ => i).reset) } } + + { + // F has a Functor and L has no Semigroup + implicit val F: Functor[ListWrapper] = ListWrapper.functor + + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", FunctorTests[WriterT[ListWrapper, ListWrapper[Int], ?]].functor[Int, Int, Int]) + checkAll("Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Functor[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + // just making sure this resolves; it's tested above + Functor[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + } + + // We have varying instances available depending on `F` and `L`. + // We also battle some inference issues with `Id`. + // Below we go through some gymnastics in order to test both the implicit + // resolution and the laws of these various instances. + { + // F has an Apply and L has a Semigroup + implicit val F: Apply[ListWrapper] = ListWrapper.monadCombine + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplyTests[WriterT[ListWrapper, ListWrapper[Int], ?]].apply[Int, Int, Int]) + checkAll("Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Apply[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + } + + { + // F has a FlatMap and L has a Semigroup + implicit val F: FlatMap[ListWrapper] = ListWrapper.monadCombine + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", FlatMapTests[WriterT[ListWrapper, ListWrapper[Int], ?]].flatMap[Int, Int, Int]) + checkAll("FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + FlatMap[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + FlatMap[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + FlatMap[Logged] + } + + { + // F has an Applicative and L has a Monoid + implicit val F: Applicative[ListWrapper] = ListWrapper.monadCombine + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplicativeTests[WriterT[ListWrapper, ListWrapper[Int], ?]].applicative[Int, Int, Int]) + checkAll("Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + Applicative[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + Applicative[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + Applicative[Logged] + } + + { + // F has a Monad and L has a Monoid + implicit val F: Monad[ListWrapper] = ListWrapper.monadCombine + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monad[Int, Int, Int]) + checkAll("Monad[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Monad[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + Applicative[WriterT[Id, ListWrapper[Int], ?]] + FlatMap[WriterT[Id, ListWrapper[Int], ?]] + Monad[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + Applicative[Writer[ListWrapper[Int], ?]] + FlatMap[Writer[ListWrapper[Int], ?]] + Monad[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + Applicative[Logged] + FlatMap[Logged] + Monad[Logged] + } } From 4e0bd7c1d6ee51645b975cb6952d20817501b78c Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 10:39:46 +0000 Subject: [PATCH 02/14] Minor additions to validated tests conditions Some minor additional condition checks to existing validated tests. --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 448be9131e..d7ea2edb5d 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -60,6 +60,8 @@ class ValidatedTests extends CatsSuite { if (v.isInvalid) { v.forall(p) should === (true) v.exists(p) should === (false) + } else { + v.forall(p) should === (v.exists(p)) } } } @@ -69,6 +71,7 @@ class ValidatedTests extends CatsSuite { var count = 0 v.foreach(_ => count += 1) v.isValid should === (count == 1) + v.isInvalid should === (count == 0) } } From 351cca16cfa6d03979f01a0737faccb3bbd1329a Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 12:13:23 +0000 Subject: [PATCH 03/14] Add a test for Show[Map] Adds a test for Show[Map] --- tests/src/test/scala/cats/tests/MapTests.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index 6de167aa91..aae934e7e4 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -9,4 +9,14 @@ class MapTests extends CatsSuite { checkAll("Map[Int, Int] with Option", TraverseTests[Map[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Map[Int, ?]]", SerializableTests.serializable(Traverse[Map[Int, ?]])) + + test("show isn't empty and is formatted as expected") { + val mapShow = implicitly[Show[Map[Int, String]]] + + forAll { (map: Map[Int, String]) => + val show = mapShow.show(map) + show.nonEmpty should === (true) + show.startsWith("Map(") should === (true) + } + } } From e4798a79f42b032052d0ba8dae6203cc696fcc7a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 08:36:07 -0500 Subject: [PATCH 04/14] Update simulacrum and change bricks to catalysts I believe this change replaces #633. --- build.sbt | 6 +++--- laws/src/main/scala/cats/laws/SerializableLaws.scala | 2 +- tests/src/test/scala/cats/tests/CatsSuite.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index c3238771e4..f4f738fbbf 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("snapshots") ), libraryDependencies ++= Seq( - "com.github.mpilquist" %%% "simulacrum" % "0.4.0", + "com.github.mpilquist" %%% "simulacrum" % "0.5.0", "org.spire-math" %%% "algebra" % "0.3.1", "org.spire-math" %%% "algebra-std" % "0.3.1", "org.typelevel" %%% "machinist" % "0.4.1", @@ -135,7 +135,7 @@ lazy val laws = crossProject.crossType(CrossType.Pure) .settings(disciplineDependencies:_*) .settings(libraryDependencies ++= Seq( "org.spire-math" %%% "algebra-laws" % "0.3.1", - "com.github.inthenow" %%% "bricks-platform" % "0.0.1")) + "org.typelevel" %%% "catalysts-platform" % "0.0.2")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) @@ -170,7 +170,7 @@ lazy val tests = crossProject.crossType(CrossType.Pure) .settings(noPublishSettings:_*) .settings(libraryDependencies ++= Seq( "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test", - "com.github.inthenow" %%% "bricks-platform" % "0.0.1" % "test")) + "org.typelevel" %%% "catalysts-platform" % "0.0.2" % "test")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index 02eeef0bae..7d505c9715 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -4,7 +4,7 @@ package laws import org.scalacheck.Prop import org.scalacheck.Prop.{ False, Proof, Result } -import bricks.Platform +import catalysts.Platform /** * Check for Java Serializability. diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index 4b7b48f9b0..8594d8668f 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import bricks.Platform +import catalysts.Platform import cats.std.AllInstances import cats.syntax.{AllSyntax, EqOps} From e8b9e90f75b0e57e705bd6b8c44de2428f4300e6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 09:39:11 -0500 Subject: [PATCH 05/14] Tests: XorT with Id as F should be consistent with Xor Add a bunch of tests that XorT[Id, A, B] methods are consistent with Xor[A, B]. --- core/src/main/scala/cats/data/Streaming.scala | 2 +- .../src/test/scala/cats/tests/XorTTests.scala | 70 ++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index f9f8530c9e..4bfd4316d9 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -89,7 +89,7 @@ sealed abstract class Streaming[A] { lhs => /** * A variant of fold, used for constructing streams. * - * The only difference is that foldStream will preserve deferred + * The only difference is that foldStreaming will preserve deferred * streams. This makes it more appropriate to use in situations * where the stream's laziness must be preserved. */ diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 79972bcc10..bafd721bc3 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -1,6 +1,6 @@ -package cats.tests +package cats +package tests -import cats.{Id, MonadError} import cats.data.{Xor, XorT} import cats.laws.discipline.{BifunctorTests, MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ @@ -103,4 +103,70 @@ class XorTTests extends CatsSuite { xort.subflatMap(f) should === (XorT(xort.value.map(_.flatMap(f)))) } } + + test("fold with Id consistent with Xor fold") { + forAll { (xort: XorT[Id, String, Int], f: String => Long, g: Int => Long) => + xort.fold(f, g) should === (xort.value.fold(f, g)) + } + } + + test("getOrElse with Id consistent with Xor getOrElse") { + forAll { (xort: XorT[Id, String, Int], i: Int) => + xort.getOrElse(i) should === (xort.value.getOrElse(i)) + } + } + + test("forall with Id consistent with Xor forall") { + forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => + xort.forall(f) should === (xort.value.forall(f)) + } + } + + test("exists with Id consistent with Xor exists") { + forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => + xort.exists(f) should === (xort.value.exists(f)) + } + } + + test("leftMap with Id consistent with Xor leftMap") { + forAll { (xort: XorT[Id, String, Int], f: String => Long) => + xort.leftMap(f).value should === (xort.value.leftMap(f)) + } + } + + test("compare with Id consistent with Xor compare") { + forAll { (x: XorT[Id, String, Int], y: XorT[Id, String, Int]) => + x.compare(y) should === (x.value.compare(y.value)) + } + } + + test("=== with Id consistent with Xor ===") { + forAll { (x: XorT[Id, String, Int], y: XorT[Id, String, Int]) => + x === y should === (x.value === y.value) + } + } + + test("traverse with Id consistent with Xor traverse") { + forAll { (x: XorT[Id, String, Int], f: Int => Option[Long]) => + x.traverse(f).map(_.value) should === (x.value.traverse(f)) + } + } + + test("foldLeft with Id consistent with Xor foldLeft") { + forAll { (x: XorT[Id, String, Int], l: Long, f: (Long, Int) => Long) => + x.foldLeft(l)(f) should === (x.value.foldLeft(l)(f)) + } + } + + test("foldRight with Id consistent with Xor foldRight") { + forAll { (x: XorT[Id, String, Int], l: Eval[Long], f: (Int, Eval[Long]) => Eval[Long]) => + x.foldRight(l)(f) should === (x.value.foldRight(l)(f)) + } + } + + test("merge with Id consistent with Xor merge") { + forAll { (x: XorT[Id, Int, Int]) => + x.merge should === (x.value.merge) + } + } } From 1202059d9fde5d007a05728eb3bcf765fddd5b54 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 15:08:38 +0000 Subject: [PATCH 06/14] Update to test using map.show syntax --- tests/src/test/scala/cats/tests/MapTests.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index aae934e7e4..d11b81495c 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -11,12 +11,10 @@ class MapTests extends CatsSuite { checkAll("Traverse[Map[Int, ?]]", SerializableTests.serializable(Traverse[Map[Int, ?]])) test("show isn't empty and is formatted as expected") { - val mapShow = implicitly[Show[Map[Int, String]]] - forAll { (map: Map[Int, String]) => - val show = mapShow.show(map) - show.nonEmpty should === (true) - show.startsWith("Map(") should === (true) + map.show.nonEmpty should === (true) + map.show.startsWith("Map(") should === (true) + map.show should === (implicitly[Show[Map[Int, String]]].show(map)) } } } From fc27b1f4412c9c529a6a78c0b10760d950c8cbc5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 10:25:34 -0500 Subject: [PATCH 07/14] Change ProdInstance to ProdInstances for consistency This makes the `Prod` instances hierarchy consistent with instances for other data types. Mostly this is to make them easier to find with grepping. --- core/src/main/scala/cats/data/Prod.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index bd70fb9396..c3c3a20c25 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -21,7 +21,7 @@ object Prod extends ProdInstances { Some((x.first, x.second)) } -private[data] sealed abstract class ProdInstances extends ProdInstance0 { +private[data] sealed abstract class ProdInstances extends ProdInstances0 { implicit def prodAlternative[F[_], G[_]](implicit FF: Alternative[F], GG: Alternative[G]): Alternative[Lambda[X => Prod[F, G, X]]] = new ProdAlternative[F, G] { def F: Alternative[F] = FF def G: Alternative[G] = GG @@ -33,35 +33,35 @@ private[data] sealed abstract class ProdInstances extends ProdInstance0 { } } -sealed abstract class ProdInstance0 extends ProdInstance1 { +sealed abstract class ProdInstances0 extends ProdInstances1 { implicit def prodMonoidK[F[_], G[_]](implicit FF: MonoidK[F], GG: MonoidK[G]): MonoidK[Lambda[X => Prod[F, G, X]]] = new ProdMonoidK[F, G] { def F: MonoidK[F] = FF def G: MonoidK[G] = GG } } -sealed abstract class ProdInstance1 extends ProdInstance2 { +sealed abstract class ProdInstances1 extends ProdInstances2 { implicit def prodSemigroupK[F[_], G[_]](implicit FF: SemigroupK[F], GG: SemigroupK[G]): SemigroupK[Lambda[X => Prod[F, G, X]]] = new ProdSemigroupK[F, G] { def F: SemigroupK[F] = FF def G: SemigroupK[G] = GG } } -sealed abstract class ProdInstance2 extends ProdInstance3 { +sealed abstract class ProdInstances2 extends ProdInstances3 { implicit def prodApplicative[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[Lambda[X => Prod[F, G, X]]] = new ProdApplicative[F, G] { def F: Applicative[F] = FF def G: Applicative[G] = GG } } -sealed abstract class ProdInstance3 extends ProdInstance4 { +sealed abstract class ProdInstances3 extends ProdInstances4 { implicit def prodApply[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[Lambda[X => Prod[F, G, X]]] = new ProdApply[F, G] { def F: Apply[F] = FF def G: Apply[G] = GG } } -sealed abstract class ProdInstance4 { +sealed abstract class ProdInstances4 { implicit def prodFunctor[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[Lambda[X => Prod[F, G, X]]] = new ProdFunctor[F, G] { def F: Functor[F] = FF def G: Functor[G] = GG From ce4d738117aac3e5f3b4fbe018994317274f9e72 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 10:45:22 -0500 Subject: [PATCH 08/14] Streaming and StreamingT extend Product with Serializable This helps type inference by fixing the least upper bound of subtypes of Streaming(T). Before: ```scala scala> val x = if (true) Cons(1, Now(Streaming.empty[Int])) else Streaming.Empty[Int]() x: Product with Serializable with cats.data.Streaming[Int] = Streaming(1, ...) ``` After: ```scala scala> val x = if (true) Cons(1, Now(Streaming.empty[Int])) else Streaming.Empty[Int]() x: cats.data.Streaming[Int] = Streaming(1, ...) ``` --- core/src/main/scala/cats/data/Streaming.scala | 2 +- core/src/main/scala/cats/data/StreamingT.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index f9f8530c9e..30848ad516 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -58,7 +58,7 @@ import scala.collection.mutable * constructed with `Foldable#foldRight`, and that `.map` and * `.flatMap` operations over the tail will be safely trampolined. */ -sealed abstract class Streaming[A] { lhs => +sealed abstract class Streaming[A] extends Product with Serializable { lhs => import Streaming.{Empty, Wait, Cons} diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index d0c76a5b42..86c2734509 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -14,7 +14,7 @@ import cats.syntax.all._ * not support many methods on `Streaming[A]` which return immediate * values. */ -sealed abstract class StreamingT[F[_], A] { lhs => +sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lhs => import StreamingT.{Empty, Wait, Cons} From fd228b20a96095c671d2a164bea96f20ba2864dd Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 11:53:17 -0500 Subject: [PATCH 09/14] Add law-checking for Validated PartialOrder and Eq instances --- .../test/scala/cats/tests/ValidatedTests.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 448be9131e..5ae4df98d6 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -20,6 +20,21 @@ class ValidatedTests extends CatsSuite { checkAll("Traverse[Validated[String,?]]", SerializableTests.serializable(Traverse[Validated[String,?]])) checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) + checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) + + { + implicit val S = ListWrapper.partialOrder[String] + implicit val I = ListWrapper.partialOrder[Int] + checkAll("Validated[ListWrapper[String], ListWrapper[Int]]", OrderLaws[Validated[ListWrapper[String], ListWrapper[Int]]].partialOrder) + checkAll("PartialOrder[Validated[ListWrapper[String], ListWrapper[Int]]]", SerializableTests.serializable(PartialOrder[Validated[ListWrapper[String], ListWrapper[Int]]])) + } + + { + implicit val S = ListWrapper.eqv[String] + implicit val I = ListWrapper.eqv[Int] + checkAll("Validated[ListWrapper[String], ListWrapper[Int]]", OrderLaws[Validated[ListWrapper[String], ListWrapper[Int]]].eqv) + checkAll("Eq[Validated[ListWrapper[String], ListWrapper[Int]]]", SerializableTests.serializable(Eq[Validated[ListWrapper[String], ListWrapper[Int]]])) + } test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) From bd3176b9987921cce4cf2aec4d8e0851c45f799c Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Sun, 15 Nov 2015 12:07:32 -0500 Subject: [PATCH 10/14] Fix outdated example in Scaladoc for catchOnly --- core/src/main/scala/cats/data/Xor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index e991bae19f..19a98fa8d9 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -217,7 +217,7 @@ trait XorFunctions { * the resulting `Xor`. Uncaught exceptions are propagated. * * For example: {{{ - * val result: NumberFormatException Xor Int = catching[NumberFormatException] { "foo".toInt } + * val result: NumberFormatException Xor Int = catchOnly[NumberFormatException] { "foo".toInt } * }}} */ def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = From b86d73949acc0ca18fb9ab74daf64838ca520b63 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 17:13:55 +0000 Subject: [PATCH 11/14] Add tests to cover derived methods leftMap and rightMap Add tests to cover derived methods leftMap and rightMap --- laws/src/main/scala/cats/laws/BifunctorLaws.scala | 7 +++++++ .../main/scala/cats/laws/discipline/BifunctorTests.scala | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/BifunctorLaws.scala b/laws/src/main/scala/cats/laws/BifunctorLaws.scala index 95af05c55d..60f5067554 100644 --- a/laws/src/main/scala/cats/laws/BifunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/BifunctorLaws.scala @@ -15,6 +15,13 @@ trait BifunctorLaws[F[_, _]] { def bifunctorComposition[A, B, C, X, Y, Z](fa: F[A, X], f: A => B, f2: B => C, g: X => Y, g2: Y => Z): IsEq[F[C, Z]] = { fa.bimap(f, g).bimap(f2, g2) <-> fa.bimap(f andThen f2, g andThen g2) } + + def bifunctorLeftMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = + F.leftMap(fa)(identity) <-> fa + + def bifunctorRightMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = + F.rightMap(fa)(identity) <-> fa + } object BifunctorLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala index 1f746a1893..5bce87afa5 100644 --- a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala @@ -23,7 +23,9 @@ trait BifunctorTests[F[_, _]] extends Laws { name = "Bifunctor", parent = None, "Bifunctor Identity" -> forAll(laws.bifunctorIdentity[A, B] _), - "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _) + "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _), + "Bifunctor leftMap Identity" -> forAll(laws.bifunctorLeftMapIdentity[A, B] _), + "Bifunctor rightMap Identity" -> forAll(laws.bifunctorRightMapIdentity[A, B] _) ) } } From 678760818398e6e895dc9a798cda7cbddf734ebc Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 12:22:46 -0500 Subject: [PATCH 12/14] Add a couple Validated.fromOption tests --- .../src/test/scala/cats/tests/ValidatedTests.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 448be9131e..6350b651c9 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList, Validated} +import cats.data.{NonEmptyList, Validated, Xor} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} @@ -113,4 +113,16 @@ class ValidatedTests extends CatsSuite { (Validated.valid(4) andThen even) should === (Validated.valid(4)) (Validated.invalid("foo") andThen even) should === (Validated.invalid("foo")) } + + test("fromOption consistent with Xor.fromOption"){ + forAll { (o: Option[Int], s: String) => + Validated.fromOption(o, s) should === (Xor.fromOption(o, s).toValidated) + } + } + + test("fromOption consistent with toOption"){ + forAll { (o: Option[Int], s: String) => + Validated.fromOption(o, s).toOption should === (o) + } + } } From d744871af424e37a22d64c39222f805ff9af9344 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 18:48:40 +0000 Subject: [PATCH 13/14] Add leftMap and rightMap to syntax and add associativity tests for leftMap and rightMap composition --- core/src/main/scala/cats/syntax/bifunctor.scala | 5 +++++ laws/src/main/scala/cats/laws/BifunctorLaws.scala | 12 ++++++++++-- .../scala/cats/laws/discipline/BifunctorTests.scala | 8 ++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/syntax/bifunctor.scala b/core/src/main/scala/cats/syntax/bifunctor.scala index 720200ba02..028fa5c442 100644 --- a/core/src/main/scala/cats/syntax/bifunctor.scala +++ b/core/src/main/scala/cats/syntax/bifunctor.scala @@ -10,5 +10,10 @@ trait BifunctorSyntax { } class BifunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifunctor[F]) { + def bimap[C, D](f: A => C, g: B => D): F[C,D] = F.bimap(fab)(f,g) + + def leftMap[C](f: A => C): F[C, B] = F.leftMap(fab)(f) + + def rightMap[D](f: B => D): F[A, D] = F.rightMap(fab)(f) } diff --git a/laws/src/main/scala/cats/laws/BifunctorLaws.scala b/laws/src/main/scala/cats/laws/BifunctorLaws.scala index 60f5067554..acd7f4c545 100644 --- a/laws/src/main/scala/cats/laws/BifunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/BifunctorLaws.scala @@ -17,10 +17,18 @@ trait BifunctorLaws[F[_, _]] { } def bifunctorLeftMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = - F.leftMap(fa)(identity) <-> fa + fa.leftMap(identity) <-> fa def bifunctorRightMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = - F.rightMap(fa)(identity) <-> fa + fa.rightMap(identity) <-> fa + + def bifunctorLeftMapComposition[A, B, C, D](fa: F[A, B], f: A => C, g: C => D): IsEq[F[D, B]] = { + fa.leftMap(f).leftMap(g) <-> fa.leftMap(f andThen g) + } + + def bifunctorRightMapComposition[A, B, C, D](fa: F[A, B], f: B => C, g: C => D): IsEq[F[A, D]] = { + fa.rightMap(f).rightMap(g) <-> fa.rightMap(f andThen g) + } } diff --git a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala index 5bce87afa5..88e5671066 100644 --- a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala @@ -17,7 +17,9 @@ trait BifunctorTests[F[_, _]] extends Laws { ArbB2: Arbitrary[B => B2], ArbB3: Arbitrary[B2 => B3], EqFAB: Eq[F[A, B]], - EqFCZ: Eq[F[A3, B3]] + EqFCZ: Eq[F[A3, B3]], + EqFA3B: Eq[F[A3, B]], + EqFAB3: Eq[F[A, B3]] ): RuleSet = { new DefaultRuleSet( name = "Bifunctor", @@ -25,7 +27,9 @@ trait BifunctorTests[F[_, _]] extends Laws { "Bifunctor Identity" -> forAll(laws.bifunctorIdentity[A, B] _), "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _), "Bifunctor leftMap Identity" -> forAll(laws.bifunctorLeftMapIdentity[A, B] _), - "Bifunctor rightMap Identity" -> forAll(laws.bifunctorRightMapIdentity[A, B] _) + "Bifunctor rightMap Identity" -> forAll(laws.bifunctorRightMapIdentity[A, B] _), + "Bifunctor leftMap associativity" -> forAll(laws.bifunctorLeftMapComposition[A, B, A2, A3] _), + "Bifunctor rightMap associativity" -> forAll(laws.bifunctorRightMapComposition[A, B, B2, B3] _) ) } } From d417162bb5fa027fc81edc93103072e1bd8934b0 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 14:29:27 -0500 Subject: [PATCH 14/14] Tests: Xor.to and Ior.to consistency Test that `Xor.to` and `Ior.to` are consistent with the less general `toOption` and `toList` methods --- tests/src/test/scala/cats/tests/IorTests.scala | 12 ++++++++++++ tests/src/test/scala/cats/tests/XorTests.scala | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index e34ac5009d..57e7b1ce17 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -130,4 +130,16 @@ class IorTests extends CatsSuite { iorMaybe should === (Some(ior)) } } + + test("to consistent with toList") { + forAll { (x: Int Ior String) => + x.to[List, String] should === (x.toList) + } + } + + test("to consistent with toOption") { + forAll { (x: Int Ior String) => + x.to[Option, String] should === (x.toOption) + } + } } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b684b6b790..7479817ff5 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -183,4 +183,16 @@ class XorTests extends CatsSuite { } } + test("to consistent with toList") { + forAll { (x: Int Xor String) => + x.to[List, String] should === (x.toList) + } + } + + test("to consistent with toOption") { + forAll { (x: Int Xor String) => + x.to[Option, String] should === (x.toOption) + } + } + }