diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 487c17d3f4..e97ae3cbaf 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -310,6 +310,30 @@ private[cats] trait EvalInstances extends EvalInstances0 { } } + implicit val catsReducibleForEval: Reducible[Eval] = + new Reducible[Eval] { + def foldLeft[A, B](fa: Eval[A], b: B)(f: (B, A) => B): B = + f(b, fa.value) + def foldRight[A, B](fa: Eval[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.flatMap(f(_, lb)) + + override def reduce[A](fa: Eval[A])(implicit A: Semigroup[A]): A = + fa.value + override def reduceLeft[A](fa: Eval[A])(f: (A, A) => A): A = + fa.value + def reduceLeftTo[A, B](fa: Eval[A])(f: A => B)(g: (B, A) => B): B = + f(fa.value) + override def reduceRight[A](fa: Eval[A])(f: (A, Eval[A]) => Eval[A]): Eval[A] = + fa + def reduceRightTo[A, B](fa: Eval[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.map(f) + override def reduceRightOption[A](fa: Eval[A])(f: (A, Eval[A]) => Eval[A]): Eval[Option[A]] = + fa.map(Some(_)) + override def reduceRightToOption[A, B](fa: Eval[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + fa.map { a => Some(f(a)) } + override def size[A](f: Eval[A]): Long = 1L + } + implicit def catsOrderForEval[A: Order]: Order[Eval[A]] = new Order[Eval[A]] { def compare(lx: Eval[A], ly: Eval[A]): Int = diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 1643380a32..fabeb776bc 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -27,8 +27,8 @@ package object cats { * encodes pure unary function application. */ type Id[A] = A - implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with Traverse[Id] = - new Bimonad[Id] with Monad[Id] with Traverse[Id] { + implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with Traverse[Id] with Reducible[Id] = + new Bimonad[Id] with Monad[Id] with Traverse[Id] with Reducible[Id] { def pure[A](a: A): A = a def extract[A](a: A): A = a def flatMap[A, B](a: A)(f: A => B): B = f(a) @@ -48,6 +48,22 @@ package object cats { f(a, lb) def traverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Applicative[G]): G[B] = f(a) + override def reduce[A](fa: Id[A])(implicit A: Semigroup[A]): A = + fa + def reduceLeftTo[A, B](fa: Id[A])(f: A => B)(g: (B, A) => B): B = + f(fa) + override def reduceLeft[A](fa: Id[A])(f: (A, A) => A): A = + fa + override def reduceLeftToOption[A, B](fa: Id[A])(f: A => B)(g: (B, A) => B): Option[B] = + Some(f(fa)) + override def reduceRight[A](fa: Id[A])(f: (A, Eval[A]) => Eval[A]): Eval[A] = + Now(fa) + def reduceRightTo[A, B](fa: Id[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + Now(f(fa)) + override def reduceRightToOption[A, B](fa: Id[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + Now(Some(f(fa))) + override def reduceMap[A, B](fa: Id[A])(f: A => B)(implicit B: Semigroup[B]): B = f(fa) + override def size[A](fa: Id[A]): Long = 1L } type Eq[A] = cats.kernel.Eq[A] diff --git a/laws/src/main/scala/cats/laws/ReducibleLaws.scala b/laws/src/main/scala/cats/laws/ReducibleLaws.scala index 937a23c35d..bdfac81152 100644 --- a/laws/src/main/scala/cats/laws/ReducibleLaws.scala +++ b/laws/src/main/scala/cats/laws/ReducibleLaws.scala @@ -22,11 +22,30 @@ trait ReducibleLaws[F[_]] extends FoldableLaws[F] { ): IsEq[B] = fa.reduceMap(f) <-> fa.reduceRightTo(f)((a, eb) => eb.map(f(a) |+| _)).value + def reduceRightToConsistentWithReduceRightToOption[A, B]( + fa: F[A], + f: A => B + )(implicit + B: Semigroup[B] + ): IsEq[Option[B]] = + fa.reduceRightToOption(f)((a, eb) => eb.map(f(a) |+| _)).value <-> + fa.reduceRightTo(f)((a, eb) => eb.map(f(a) |+| _)).map(Option(_)).value + + def reduceRightConsistentWithReduceRightOption[A](fa: F[A], f: (A, A) => A): IsEq[Option[A]] = + fa.reduceRight((a1, e2) => Now(f(a1, e2.value))).map(Option(_)).value <-> + fa.reduceRightOption((a1, e2) => Now(f(a1, e2.value))).value + + def reduceReduceLeftConsistent[B](fa: F[B])(implicit B: Semigroup[B]): IsEq[B] = + fa.reduce <-> fa.reduceLeft(B.combine) + def traverseConsistent[G[_]: Applicative, A, B](fa: F[A], f: A => G[B]): IsEq[G[Unit]] = fa.traverse1_(f) <-> fa.traverse_(f) def sequenceConsistent[G[_]: Applicative, A](fa: F[G[A]]): IsEq[G[Unit]] = fa.sequence1_ <-> fa.sequence_ + + def sizeConsistent[A](fa: F[A]): IsEq[Long] = + fa.size <-> fa.reduceMap(_ => 1L) } object ReducibleLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala index cae5bf2730..b77034f65b 100644 --- a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala @@ -2,6 +2,9 @@ package cats package laws package discipline +import cats.instances.option._ +import cats.instances.long._ + import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Prop.forAll @@ -10,10 +13,12 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] { def reducible[G[_]: Applicative, A: Arbitrary, B: Arbitrary](implicit ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], ArbFGA: Arbitrary[F[G[A]]], ArbGB: Arbitrary[G[B]], CogenA: Cogen[A], EqG: Eq[G[Unit]], + EqA: Eq[A], EqB: Eq[B], MonoidB: Monoid[B] ): RuleSet = @@ -22,8 +27,15 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] { parent = Some(foldable[A, B]), "reduceLeftTo consistent with reduceMap" -> forAll(laws.reduceLeftToConsistentWithReduceMap[A, B] _), "reduceRightTo consistent with reduceMap" -> forAll(laws.reduceRightToConsistentWithReduceMap[A, B] _), + "reduceRightTo consistent with reduceRightToOption" -> + forAll(laws.reduceRightToConsistentWithReduceRightToOption[A, B] _), + "reduceRight consistent with reduceRightOption" -> + forAll(laws.reduceRightConsistentWithReduceRightOption[A] _), + "reduce consistent with reduceLeft" -> + forAll(laws.reduceReduceLeftConsistent[B] _), "traverse1_ consistent with traverse_" -> forAll(laws.traverseConsistent[G, A, B] _), - "sequence1_ consistent with sequence_" -> forAll(laws.sequenceConsistent[G, A] _) + "sequence1_ consistent with sequence_" -> forAll(laws.sequenceConsistent[G, A] _), + "size consistent with reduceMap" -> forAll(laws.sizeConsistent[A] _) ) } diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index d7c4100253..de7716a65d 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -3,7 +3,7 @@ package tests import scala.math.min import cats.laws.ComonadLaws -import cats.laws.discipline.{BimonadTests, CartesianTests, MonadTests, SerializableTests} +import cats.laws.discipline.{BimonadTests, CartesianTests, MonadTests, ReducibleTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.kernel.laws.{GroupLaws, OrderLaws} @@ -99,6 +99,9 @@ class EvalTests extends CatsSuite { checkAll("Bimonad[Eval]", SerializableTests.serializable(Bimonad[Eval])) checkAll("Monad[Eval]", SerializableTests.serializable(Monad[Eval])) + checkAll("Eval[Int]", ReducibleTests[Eval].reducible[Option, Int, Int]) + checkAll("Reducible[Eval]", SerializableTests.serializable(Reducible[Eval])) + checkAll("Eval[Int]", GroupLaws[Eval[Int]].group) { diff --git a/tests/src/test/scala/cats/tests/IdTests.scala b/tests/src/test/scala/cats/tests/IdTests.scala index 0465dd34b5..a55c6bfec0 100644 --- a/tests/src/test/scala/cats/tests/IdTests.scala +++ b/tests/src/test/scala/cats/tests/IdTests.scala @@ -14,4 +14,7 @@ class IdTests extends CatsSuite { checkAll("Id[Int]", TraverseTests[Id].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Id]", SerializableTests.serializable(Traverse[Id])) + + checkAll("Id[Int]", ReducibleTests[Id].reducible[Option, Int, Int]) + checkAll("Reducible[Id]", SerializableTests.serializable(Reducible[Id])) }