From 665ee72a8c3160f636f534a10072d48e0539a951 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 22 Feb 2016 21:27:42 -0800 Subject: [PATCH] Add Reducible laws --- .../main/scala/cats/laws/ReducibleLaws.scala | 29 +++++++++++++++++++ .../cats/laws/discipline/ReducibleTests.scala | 27 +++++++++++++++++ .../test/scala/cats/tests/OneAndTests.scala | 5 +++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 laws/src/main/scala/cats/laws/ReducibleLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala diff --git a/laws/src/main/scala/cats/laws/ReducibleLaws.scala b/laws/src/main/scala/cats/laws/ReducibleLaws.scala new file mode 100644 index 0000000000..5075a97ab6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ReducibleLaws.scala @@ -0,0 +1,29 @@ +package cats +package laws + +import cats.implicits._ + +trait ReducibleLaws[F[_]] extends FoldableLaws[F] { + implicit def F: Reducible[F] + + def reduceLeftToConsistentWithReduceMap[A, B]( + fa: F[A], + f: A => B + )(implicit + B: Semigroup[B] + ): IsEq[B] = + fa.reduceMap(f) <-> fa.reduceLeftTo(f)((b, a) => b |+| f(a)) + + def reduceRightToConsistentWithReduceMap[A, B]( + fa: F[A], + f: A => B + )(implicit + B: Semigroup[B] + ): IsEq[B] = + fa.reduceMap(f) <-> fa.reduceRightTo(f)((a, eb) => eb.map(f(a) |+| _)).value +} + +object ReducibleLaws { + def apply[F[_]](implicit ev: Reducible[F]): ReducibleLaws[F] = + new ReducibleLaws[F] { def F: Reducible[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala new file mode 100644 index 0000000000..cb1f343aa6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala @@ -0,0 +1,27 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll + +trait ReducibleTests[F[_]] extends FoldableTests[F] { + def laws: ReducibleLaws[F] + + def reducible[A: Arbitrary, B: Arbitrary](implicit + ArbFA: Arbitrary[F[A]], + B: Monoid[B], + EqB: Eq[B] + ): RuleSet = + new DefaultRuleSet( + name = "reducible", + parent = Some(foldable[A, B]), + "reduceLeftTo consistent with reduceMap" -> forAll(laws.reduceLeftToConsistentWithReduceMap[A, B] _), + "reduceRightTo consistent with reduceMap" -> forAll(laws.reduceRightToConsistentWithReduceMap[A, B] _) + ) +} + +object ReducibleTests { + def apply[F[_] : Reducible]: ReducibleTests[F] = + new ReducibleTests[F] { def laws: ReducibleLaws[F] = ReducibleLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index d6081b3cde..fe9bf60bb6 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -4,7 +4,7 @@ package tests import algebra.laws.{GroupLaws, OrderLaws} import cats.data.{NonEmptyList, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests} +import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} import cats.laws.discipline.eq._ @@ -16,6 +16,9 @@ class OneAndTests extends CatsSuite { checkAll("OneAnd[List, Int] with Option", TraverseTests[OneAnd[List, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[OneAnd[List, A]]", SerializableTests.serializable(Traverse[OneAnd[List, ?]])) + checkAll("OneAnd[List, Int]", ReducibleTests[OneAnd[List, ?]].reducible[Int, Int]) + checkAll("Reducible[OneAnd[List, ?]]", SerializableTests.serializable(Reducible[OneAnd[List, ?]])) + implicit val iso = CartesianTests.Isomorphisms.invariant[OneAnd[ListWrapper, ?]](OneAnd.oneAndFunctor(ListWrapper.functor)) // Test instances that have more general constraints