From bb7277b1876419511cb7e10af4249ac43e1e553d Mon Sep 17 00:00:00 2001 From: satorg Date: Mon, 12 Jun 2023 22:11:12 -0700 Subject: [PATCH 1/2] add Order and PartialOrder to OneAnd --- core/src/main/scala/cats/data/OneAnd.scala | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index efc5f44095..5e3c390fd1 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -147,7 +147,14 @@ sealed abstract private[data] class OneAndInstances extends OneAndLowPriority0 { } - implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = _ === _ + implicit def catsDataOrderForOneAnd[A, F[_]](implicit A: Order[A], FA: Order[F[A]]): Order[OneAnd[F, A]] = + new Order[OneAnd[F, A]] { + def compare(x: OneAnd[F, A], y: OneAnd[F, A]): Int = + A.compare(x.head, y.head) match { + case 0 => FA.compare(x.tail, y.tail) + case neq => neq + } + } implicit def catsDataShowForOneAnd[A, F[_]](implicit A: Show[A], FA: Show[F[A]]): Show[OneAnd[F, A]] = _.show @@ -268,6 +275,9 @@ sealed abstract private[data] class OneAndLowPriority1 extends OneAndLowPriority } sealed abstract private[data] class OneAndLowPriority0_5 extends OneAndLowPriority1 { + + implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = _ === _ + implicit def catsDataReducibleForOneAnd[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, *]] = new NonEmptyReducible[OneAnd[F, *], F] { override def split[A](fa: OneAnd[F, A]): (A, F[A]) = (fa.head, fa.tail) @@ -280,6 +290,20 @@ sealed abstract private[data] class OneAndLowPriority0_5 extends OneAndLowPriori } sealed abstract private[data] class OneAndLowPriority0 extends OneAndLowPriority0_5 { + + implicit def catsDataPartialOrderForOneAnd[A, F[_]](implicit + A: PartialOrder[A], + FA: PartialOrder[F[A]] + ): PartialOrder[OneAnd[F, A]] = + new PartialOrder[OneAnd[F, A]] { + def partialCompare(x: OneAnd[F, A], y: OneAnd[F, A]): Double = { + A.partialCompare(x.head, y.head) match { + case 0.0 => FA.partialCompare(x.tail, y.tail) + case neq => neq + } + } + } + implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: Alternative[F] From 9623b83b7c39199fe8bd804ff8117924b004d7b1 Mon Sep 17 00:00:00 2001 From: satorg Date: Mon, 12 Jun 2023 22:57:11 -0700 Subject: [PATCH 2/2] add tests --- .../test/scala/cats/tests/OneAndSuite.scala | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/OneAndSuite.scala b/tests/shared/src/test/scala/cats/tests/OneAndSuite.scala index 7aed3a1754..d7b57e889b 100644 --- a/tests/shared/src/test/scala/cats/tests/OneAndSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/OneAndSuite.scala @@ -21,13 +21,25 @@ package cats.tests -import cats.{Alternative, Applicative, Foldable, Functor, Monad, SemigroupK, Traverse} +import cats.Alternative +import cats.Applicative +import cats.Foldable +import cats.Functor +import cats.Monad +import cats.SemigroupK +import cats.Traverse import cats.data.OneAnd +import cats.kernel.Eq +import cats.kernel.Order +import cats.kernel.PartialOrder +import cats.kernel.laws.discipline.EqTests +import cats.kernel.laws.discipline.OrderTests +import cats.kernel.laws.discipline.PartialOrderTests +import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.syntax.foldable._ -import cats.syntax.eq._ +import cats.syntax.order._ import org.scalacheck.Prop._ import org.scalacheck.Test.Parameters @@ -36,6 +48,47 @@ class OneAndSuite extends CatsSuite { implicit override val scalaCheckTestParameters: Parameters = Parameters.default.withMinSuccessfulTests(20).withMaxSize(Parameters.default.minSize + 5) + // Test kernel instances + { + import Helpers.Eqed + + checkAll("OneAnd[F, A]", EqTests[OneAnd[ListWrapper, Eqed]].eqv) + checkAll("Eq[OneAnd[F, A]]", SerializableTests.serializable(Eq[OneAnd[ListWrapper, Eqed]])) + + property("Eq[OneAnd[F, A]]: must be consistent with Eq[Tuple2[A, F[A]]]") { + forAll { (x: OneAnd[ListWrapper, Eqed], y: OneAnd[ListWrapper, Eqed]) => + assertEquals(x.eqv(y), (x.head, x.tail).eqv((y.head, y.tail))) + } + } + } + { + import Helpers.POrd + + implicit val partialOrder: PartialOrder[ListWrapper[POrd]] = ListWrapper.partialOrder + checkAll("OneAnd[F, A]", PartialOrderTests[OneAnd[ListWrapper, POrd]].partialOrder) + checkAll("PartialOrder[OneAnd[F, A]]", SerializableTests.serializable(PartialOrder[OneAnd[ListWrapper, POrd]])) + + property("PartialOrder[OneAnd[F, A]]: must be consistent with PartialOrder[Tuple2[A, F[A]]]") { + forAll { (x: OneAnd[ListWrapper, POrd], y: OneAnd[ListWrapper, POrd]) => + // `NaN` cannot be compared directly; hence using `partialComparison` instead of `partialCompare`. + assertEquals(x.partialComparison(y), (x.head, x.tail).partialComparison((y.head, y.tail))) + } + } + } + { + import Helpers.Ord + + implicit val order: Order[ListWrapper[Ord]] = ListWrapper.order + checkAll("OneAnd[F, A]", OrderTests[OneAnd[ListWrapper, Ord]].order) + checkAll("Order[OneAnd[F, A]]", SerializableTests.serializable(Order[OneAnd[ListWrapper, Ord]])) + + property("Order[OneAnd[F, A]]: must be consistent with Order[Tuple2[A, F[A]]]") { + forAll { (x: OneAnd[ListWrapper, Ord], y: OneAnd[ListWrapper, Ord]) => + assertEquals(x.compare(y), (x.head, x.tail).compare((y.head, y.tail))) + } + } + } + { implicit val traverse: Traverse[OneAnd[ListWrapper, *]] = OneAnd.catsDataTraverseForOneAnd(ListWrapper.traverse) checkAll("OneAnd[ListWrapper, Int] with Option",