diff --git a/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala new file mode 100644 index 00000000..01e730e3 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala @@ -0,0 +1,46 @@ +package cats.derived + +import cats.{PartialOrder, Show} +import shapeless3.deriving.{Complete, Continue, K0, Labelling} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.deriving.Mirror + +@implicitNotFound("""Could not derive an instance of PartialOrder[A] where A = ${A}. +Make sure that A satisfies one of the following conditions: + * it is a case class where all fields have a PartialOrder instance + * it is a sealed trait where all subclasses have a PartialOrder instance""") +type DerivedPartialOrder[A] = Derived[PartialOrder[A]] +object DerivedPartialOrder: + type Or[A] = Derived.Or[PartialOrder[A]] + + inline def apply[A]: PartialOrder[A] = + import DerivedPartialOrder.given + summonInline[DerivedPartialOrder[A]].instance + + given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedPartialOrder[A] = + given K0.ProductInstances[PartialOrder, A] = inst.unify + new Product[PartialOrder, A] {} + + given coproduct[A](using inst: => K0.CoproductInstances[Or, A]): DerivedPartialOrder[A] = + given K0.CoproductInstances[PartialOrder, A] = inst.unify + new Coproduct[PartialOrder, A] {} + + trait Product[T[x] <: PartialOrder[x], A](using inst: K0.ProductInstances[T, A]) extends PartialOrder[A]: + + def partialCompare(x: A, y: A): Double = + inst.foldLeft2(x, y)(0: Double)( + [t] => + (acc: Double, ord: T[t], t0: t, t1: t) => { + val cmp = ord.partialCompare(t0, t1) + Complete(cmp != 0)(cmp)(acc) + } + ) + + trait Coproduct[T[x] <: PartialOrder[x], A](using inst: K0.CoproductInstances[T, A]) extends PartialOrder[A]: + + def partialCompare(x: A, y: A): Double = + inst.fold2(x, y)(Double.NaN: Double)( + [t] => (ord: T[t], t0: t, t1: t) => ord.partialCompare(t0, t1) + ) diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala index 914bbe25..db721d01 100644 --- a/core/src/main/scala-3/cats/derived/package.scala +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -28,9 +28,9 @@ extension (x: SemigroupK.type) inline def derived[F[_]]: SemigroupK[F] = Derived extension (x: MonoidK.type) inline def derived[F[_]]: MonoidK[F] = DerivedMonoidK[F] extension (x: Contravariant.type) inline def derived[F[_]]: Contravariant[F] = DerivedContravariant[F] extension (x: Invariant.type) inline def derived[F[_]]: Invariant[F] = DerivedInvariant[F] +extension (x: PartialOrder.type) inline def derived[A]: PartialOrder[A] = DerivedPartialOrder[A] -object semiauto extends PartialOrderDerivation, Instances: - +object semiauto extends Instances: inline def eq[A]: Eq[A] = DerivedEq[A] inline def hash[A]: Hash[A] = DerivedHash[A] inline def empty[A]: Empty[A] = DerivedEmpty[A] @@ -53,6 +53,7 @@ object semiauto extends PartialOrderDerivation, Instances: inline def monoidK[F[_]]: MonoidK[F] = DerivedMonoidK[F] inline def contravariant[F[_]]: Contravariant[F] = DerivedContravariant[F] inline def invariant[F[_]]: Invariant[F] = DerivedInvariant[F] + inline def partialOrder[A]: PartialOrder[A] = DerivedPartialOrder[A] object auto: object eq: @@ -120,3 +121,6 @@ object auto: object invariant: inline given [F[_]](using NotGiven[Invariant[F]]): Invariant[F] = DerivedInvariant[F] + + object partialOrder: + inline given [A](using NotGiven[PartialOrder[A]]): PartialOrder[A] = DerivedPartialOrder[A] diff --git a/core/src/main/scala-3/cats/derived/partialOrder.scala b/core/src/main/scala-3/cats/derived/partialOrder.scala deleted file mode 100644 index aa82a43a..00000000 --- a/core/src/main/scala-3/cats/derived/partialOrder.scala +++ /dev/null @@ -1,34 +0,0 @@ -package cats.derived - -import cats.PartialOrder -import shapeless3.deriving.{K0, Complete} - -trait ProductPartialOrder[T[x] <: PartialOrder[x], A](using inst: K0.ProductInstances[T, A]) extends PartialOrder[A]: - - def partialCompare(x: A, y: A): Double = - inst.foldLeft2(x, y)(0: Double)( - [t] => - (acc: Double, ord: T[t], t0: t, t1: t) => { - val cmp = ord.partialCompare(t0, t1) - Complete(cmp != 0)(cmp)(acc) - } - ) - -trait CoproductPartialOrder[T[x] <: PartialOrder[x], A](using inst: K0.CoproductInstances[T, A]) - extends PartialOrder[A]: - - def partialCompare(x: A, y: A): Double = - inst.fold2(x, y)(Double.NaN: Double)( - [t] => (ord: T[t], t0: t, t1: t) => ord.partialCompare(t0, t1) - ) - -trait PartialOrderDerivation: - extension (F: PartialOrder.type) - inline def derived[A](using gen: K0.Generic[A]): PartialOrder[A] = - gen.derive(productPartialOrder, coproductPartialOrder) - - given productPartialOrder[A](using K0.ProductInstances[PartialOrder, A]): PartialOrder[A] = - new ProductPartialOrder[PartialOrder, A] {} - - given coproductPartialOrder[A](using K0.CoproductInstances[PartialOrder, A]): PartialOrder[A] = - new CoproductPartialOrder[PartialOrder, A] {} diff --git a/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala new file mode 100644 index 00000000..3b0988ae --- /dev/null +++ b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015 Miles Sabin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats +package derived +import cats.kernel.laws.discipline.{PartialOrderTests, SerializableTests} +import org.scalacheck.{Arbitrary, Cogen} +import scala.compiletime.* + +class PartialOrderSuite extends KittensSuite: + import PartialOrderSuite.* + import TestDefns.* + + inline def partialOrderTests[A]: PartialOrderTests[A] = PartialOrderTests[A](summonInline) + + inline def testPartialOrder(context: String): Unit = + checkAll(s"$context.PartialOrder[IList[Int]]", partialOrderTests[IList[Int]].partialOrder) + checkAll(s"$context.PartialOrder[Inner]", partialOrderTests[Inner].partialOrder) + checkAll(s"$context.PartialOrder[Outer]", partialOrderTests[Outer].partialOrder) + checkAll(s"$context.PartialOrder[Interleaved[Int]]", partialOrderTests[Interleaved[Int]].partialOrder) + checkAll(s"$context.PartialOrder[Tree[Int]]", partialOrderTests[Tree[Int]].partialOrder) + checkAll(s"$context.PartialOrder[Recursive]", partialOrderTests[Recursive].partialOrder) + checkAll(s"$context.PartialOrder[Box[KeyValue]]", partialOrderTests[Box[KeyValue]].partialOrder) + checkAll( + s"$context.PartialOrder is Serialiable", + SerializableTests.serializable(summonInline[PartialOrder[Tree[Int]]]) + ) + + test(s"$context.PartialOrder respects existing instances") { + val boxKeyValue = summonInline[PartialOrder[Box[KeyValue]]] + val x = Box(KeyValue("red", 1)) + val y = Box(KeyValue("red", 2)) + val z = Box(KeyValue("blue", 1)) + assert(boxKeyValue.partialCompare(x, y) < 0) + assert(boxKeyValue.partialCompare(y, z).isNaN) + } + + locally { + import auto.partialOrder.given + testPartialOrder("auto") + } + + locally { + import semiInstances.given + testPartialOrder("semiauto") + } + +object PartialOrderSuite: + import TestDefns.* + + object semiInstances: + implicit val iList: PartialOrder[IList[Int]] = semiauto.partialOrder + implicit val inner: PartialOrder[Inner] = semiauto.partialOrder + implicit val outer: PartialOrder[Outer] = semiauto.partialOrder + implicit val interleaved: PartialOrder[Interleaved[Int]] = semiauto.partialOrder + implicit val tree: PartialOrder[Tree[Int]] = semiauto.partialOrder + implicit val recursive: PartialOrder[Recursive] = semiauto.partialOrder + implicit val boxKeyValue: PartialOrder[Box[KeyValue]] = semiauto.partialOrder + + final case class KeyValue(key: String, value: Int) + object KeyValue extends ((String, Int) => KeyValue): + implicit val arbitrary: Arbitrary[KeyValue] = Arbitrary(Arbitrary.arbitrary[(String, Int)].map(tupled)) + implicit val cogen: Cogen[KeyValue] = Cogen[(String, Int)].contramap(kv => kv.key -> kv.value) + + implicit val partialOrder: PartialOrder[KeyValue] = + PartialOrder.from((x, y) => if (x.key == y.key) x.value.toDouble - y.value.toDouble else Double.NaN) diff --git a/core/src/test/scala-3/cats/derived/PartialOrderTests.scala b/core/src/test/scala-3/cats/derived/PartialOrderTests.scala deleted file mode 100644 index 33588800..00000000 --- a/core/src/test/scala-3/cats/derived/PartialOrderTests.scala +++ /dev/null @@ -1,9 +0,0 @@ -package cats.derived - -import alleycats.* -import cats.* -import cats.derived.semiauto.* - -class PartialOrderTests { // - case class Foo(i: Int, b: Option[String]) derives PartialOrder -}