From a60e4bbf98fafca9a9949098c4539430bc485861 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 25 May 2022 12:08:27 +0100 Subject: [PATCH 1/9] Port invariant derivation to new scheme --- .../main/scala-3/cats/derived/invariant.scala | 36 ++++++++++++++----- .../main/scala-3/cats/derived/package.scala | 7 +++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala-3/cats/derived/invariant.scala b/core/src/main/scala-3/cats/derived/invariant.scala index 74d9dcc4..d8b42f8d 100644 --- a/core/src/main/scala-3/cats/derived/invariant.scala +++ b/core/src/main/scala-3/cats/derived/invariant.scala @@ -1,14 +1,32 @@ package cats.derived import cats.Invariant -import shapeless3.deriving.K1 +import shapeless3.deriving.{Const, K1} -trait GenericInvariant[T[x[_]] <: Invariant[x], F[_]](using inst: K1.Instances[T, F]) extends Invariant[F]: - def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = inst.map(fa)( - [t[_]] => (inv: T[t], t0: t[A]) => inv.imap(t0)(f)(g) - ) +import scala.annotation.implicitNotFound +import scala.compiletime.* -trait InvariantDerivation: - extension (F: Invariant.type) - inline def derived[F[_]](using gen: K1.Generic[F]): Invariant[F] = - new GenericInvariant[Invariant, F] {} +@implicitNotFound("""Could not derive an instance of Invariant[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] => T + * it is a nested type [x] => G[H[x]] where G: Invariant and H: Invariant + * it is a generic case class where all fields have an Invariant instance + * it is a generic sealed trait where all subclasses have an Invariant instance""") +type DerivedInvariant[F[_]] = Derived[Invariant[F]] +object DerivedInvariant: + type Or[F[_]] = Derived.Or[Invariant[F]] + inline def apply[F[_]]: Invariant[F] = + import DerivedInvariant.given + summonInline[DerivedInvariant[F]].instance + + given [T]: DerivedInvariant[Const[T]] = new Invariant[Const[T]]: + def imap[A, B](fa: T)(f: A => B)(g: B => A): T = fa + + given [F[_]](using inst: => K1.Instances[Or, F]): DerivedInvariant[F] = + given K1.Instances[Invariant, F] = inst.unify + new Generic[Invariant, F] {} + + trait Generic[T[x[_]] <: Invariant[x], F[_]](using inst: K1.Instances[T, F]) extends Invariant[F]: + def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = inst.map(fa)( + [t[_]] => (inv: T[t], t0: t[A]) => inv.imap(t0)(f)(g) + ) diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala index 2fa7c556..12978ec7 100644 --- a/core/src/main/scala-3/cats/derived/package.scala +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -26,8 +26,9 @@ extension (x: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTrav extension (x: NonEmptyTraverse.type) inline def derived[F[_]]: NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F] extension (x: SemigroupK.type) inline def derived[F[_]]: SemigroupK[F] = DerivedSemigroupK[F] extension (x: MonoidK.type) inline def derived[F[_]]: MonoidK[F] = DerivedMonoidK[F] +extension (x: Invariant.type) inline def derived[F[_]]: Invariant[F] = DerivedInvariant[F] -object semiauto extends ContravariantDerivation, InvariantDerivation, PartialOrderDerivation, Instances: +object semiauto extends ContravariantDerivation, PartialOrderDerivation, Instances: inline def eq[A]: Eq[A] = DerivedEq[A] inline def hash[A]: Hash[A] = DerivedHash[A] @@ -49,6 +50,7 @@ object semiauto extends ContravariantDerivation, InvariantDerivation, PartialOrd inline def show[A]: Show[A] = DerivedShow[A] inline def semigroupK[F[_]]: SemigroupK[F] = DerivedSemigroupK[F] inline def monoidK[F[_]]: MonoidK[F] = DerivedMonoidK[F] + inline def invariant[F[_]]: Invariant[F] = DerivedInvariant[F] object auto: object eq: @@ -110,3 +112,6 @@ object auto: object monoidK: inline given [F[_]](using NotGiven[MonoidK[F]]): MonoidK[F] = DerivedMonoidK[F] + + object invariant: + inline given [F[_]](using NotGiven[Invariant[F]]): Invariant[F] = DerivedInvariant[F] From 9eeb69183ce9f2616da9f236bf6a2aee8c476fa3 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 25 May 2022 14:31:45 +0100 Subject: [PATCH 2/9] Port scala 2 invariant tests to scala 3 --- ...invariant.scala => DerivedInvariant.scala} | 0 .../scala-3/cats/derived/InvariantTests.scala | 90 +++++++++++++++++-- 2 files changed, 83 insertions(+), 7 deletions(-) rename core/src/main/scala-3/cats/derived/{invariant.scala => DerivedInvariant.scala} (100%) diff --git a/core/src/main/scala-3/cats/derived/invariant.scala b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala similarity index 100% rename from core/src/main/scala-3/cats/derived/invariant.scala rename to core/src/main/scala-3/cats/derived/DerivedInvariant.scala diff --git a/core/src/test/scala-3/cats/derived/InvariantTests.scala b/core/src/test/scala-3/cats/derived/InvariantTests.scala index 71b1ac9f..50d049e3 100644 --- a/core/src/test/scala-3/cats/derived/InvariantTests.scala +++ b/core/src/test/scala-3/cats/derived/InvariantTests.scala @@ -1,11 +1,87 @@ -package cats.derived +/* + * 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. + */ -import cats.Invariant -import cats.derived.semiauto.* -import cats.derived.semiauto.given +package cats +package derived +import cats.laws.discipline.* +import cats.laws.discipline.arbitrary.* +import cats.laws.discipline.eq.* +import cats.laws.discipline.* -class InvariantTests { +import scala.compiletime.* - case class Foo[A](bar: String, baz: Option[A]) derives Invariant +class InvariantSuite extends KittensSuite: + import InvariantSuite.* + import TestDefns.* -} + inline def invariantTests[F[_]]: InvariantTests[F] = InvariantTests[F](summonInline) + + inline def testInvariant(context: String): Unit = { + checkAll(s"$context.Invariant[TreeF]", invariantTests[TreeF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[GenAdtF]", invariantTests[GenericAdtF].invariant[MiniInt, String, Boolean]) + // TODO https://github.com/typelevel/kittens/issues/473 + // checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[AndCharF]", invariantTests[AndCharF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[ListSnoc", invariantTests[ListSnoc].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[Bivariant]", invariantTests[Bivariant].invariant[MiniInt, String, Boolean]) + + checkAll(s"$context.Invariant is Serializable", SerializableTests.serializable(summonInline[Invariant[TreeF]])) + + // TODO https://github.com/typelevel/kittens/issues/476 + // test(s"$context.Invariant.imap is stack safe") { + // val I = summonInline[Invariant[ListSnoc]] + // val J = summonInline[Invariant[IList]] + // val n = 10000 + // val largeIList = IList.fromSeq(1 until n) + // val largeSnoc = Snoc.fromSeq(1 until n) :: Nil + // val actualIList = IList.toList(J.imap(largeIList)(_ + 1)(_ - 1)) + // val actualSnoc = I.imap(largeSnoc)(_ + 1)(_ - 1).flatMap(Snoc.toList) + // val expected = (2 until n + 1).toList + // assert(actualIList == expected) + // assert(actualSnoc == expected) + // } + } + + locally { + import auto.invariant.given + testInvariant("auto") + } + + locally { + import semiInstances.given + testInvariant("semiauto") + } + +object InvariantSuite: + import TestDefns.* + + type ListSnoc[A] = List[Snoc[A]] + type GenericAdtF[A] = GenericAdt[A => Boolean] + type ListFToInt[A] = List[Snoc[A => Int]] + type InterleavedF[A] = Interleaved[A => Boolean] + type AndCharF[A] = (A => Boolean, Char) + type TreeF[A] = Tree[A => Boolean] + + object semiInstances: + implicit val gadt: Invariant[GenericAdtF] = semiauto.invariant[GenericAdtF] + implicit val listSnocendo: Invariant[ListFToInt] = semiauto.invariant[ListFToInt] + // implicit val interleaveF: Invariant[InterleavedF] = semiauto.invariant[InterleavedF] + implicit val andCharF: Invariant[AndCharF] = semiauto.invariant[AndCharF] + implicit val treeF: Invariant[TreeF] = semiauto.invariant[TreeF] + implicit val pred: Invariant[Pred] = semiauto.invariant[Pred] + implicit val snoc: Invariant[ListSnoc] = semiauto.invariant[ListSnoc] + implicit val bivariant: Invariant[Bivariant] = semiauto.invariant[Bivariant] + implicit val ilist: Invariant[IList] = semiauto.invariant[IList] From bdea629486b9e5cf02cd1a30bfe2e12f80f39146 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 25 May 2022 14:33:46 +0100 Subject: [PATCH 3/9] Consistent test naming --- .../cats/derived/{InvariantTests.scala => InvariantSuite.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/src/test/scala-3/cats/derived/{InvariantTests.scala => InvariantSuite.scala} (100%) diff --git a/core/src/test/scala-3/cats/derived/InvariantTests.scala b/core/src/test/scala-3/cats/derived/InvariantSuite.scala similarity index 100% rename from core/src/test/scala-3/cats/derived/InvariantTests.scala rename to core/src/test/scala-3/cats/derived/InvariantSuite.scala From 9d7604fcc0e9b504e7cfb3ffe2be9bf62f6701fb Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 25 May 2022 14:43:17 +0100 Subject: [PATCH 4/9] WIP extra Invariant instances --- .../scala-3/cats/derived/DerivedInvariant.scala | 15 ++++++++++++++- .../scala-3/cats/derived/InvariantSuite.scala | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala-3/cats/derived/DerivedInvariant.scala b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala index d8b42f8d..25fd635f 100644 --- a/core/src/main/scala-3/cats/derived/DerivedInvariant.scala +++ b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala @@ -1,10 +1,11 @@ package cats.derived -import cats.Invariant +import cats.{Contravariant, Functor, Invariant} import shapeless3.deriving.{Const, K1} import scala.annotation.implicitNotFound import scala.compiletime.* +import scala.util.NotGiven @implicitNotFound("""Could not derive an instance of Invariant[F] where F = ${F}. Make sure that F[_] satisfies one of the following conditions: @@ -22,6 +23,18 @@ object DerivedInvariant: given [T]: DerivedInvariant[Const[T]] = new Invariant[Const[T]]: def imap[A, B](fa: T)(f: A => B)(g: B => A): T = fa + given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedInvariant[[x] =>> F[G[x]]] = + given Invariant[G] = G.unify + F.unify.compose[G] + + given [F[_], G[_]](using N: NotGiven[Or[G]], F: Or[F], G: DerivedFunctor.Or[G]): DerivedInvariant[[x] =>> F[G[x]]] = + given Functor[G] = G.unify + F.unify.composeFunctor[G] + + // given [F[_], G[_]](using N: NotGiven[Or[G]], N1: NotGiven[DerivedFunctor.Or[G]], F: Or[F], G: DerivedContravariant.Or[G]): DerivedInvariant[[x] =>> F[G[x]]] = + // given Contravariant[G] = G.unify + // F.unify.composeContravariant[G] + given [F[_]](using inst: => K1.Instances[Or, F]): DerivedInvariant[F] = given K1.Instances[Invariant, F] = inst.unify new Generic[Invariant, F] {} diff --git a/core/src/test/scala-3/cats/derived/InvariantSuite.scala b/core/src/test/scala-3/cats/derived/InvariantSuite.scala index 50d049e3..9d4e1f85 100644 --- a/core/src/test/scala-3/cats/derived/InvariantSuite.scala +++ b/core/src/test/scala-3/cats/derived/InvariantSuite.scala @@ -33,7 +33,7 @@ class InvariantSuite extends KittensSuite: checkAll(s"$context.Invariant[TreeF]", invariantTests[TreeF].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[GenAdtF]", invariantTests[GenericAdtF].invariant[MiniInt, String, Boolean]) // TODO https://github.com/typelevel/kittens/issues/473 - // checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean]) + checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[AndCharF]", invariantTests[AndCharF].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[ListSnoc", invariantTests[ListSnoc].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[Bivariant]", invariantTests[Bivariant].invariant[MiniInt, String, Boolean]) @@ -78,7 +78,7 @@ object InvariantSuite: object semiInstances: implicit val gadt: Invariant[GenericAdtF] = semiauto.invariant[GenericAdtF] implicit val listSnocendo: Invariant[ListFToInt] = semiauto.invariant[ListFToInt] - // implicit val interleaveF: Invariant[InterleavedF] = semiauto.invariant[InterleavedF] + implicit val interleaveF: Invariant[InterleavedF] = semiauto.invariant[InterleavedF] implicit val andCharF: Invariant[AndCharF] = semiauto.invariant[AndCharF] implicit val treeF: Invariant[TreeF] = semiauto.invariant[TreeF] implicit val pred: Invariant[Pred] = semiauto.invariant[Pred] From a8b5442f655875a5aecf6cb2edae9d09863da827 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 25 May 2022 15:48:58 +0100 Subject: [PATCH 5/9] Tests for invariant derivation syntax --- .../cats/derived/DerivedInvariant.scala | 8 -------- .../scala-3/cats/derived/InvariantSuite.scala | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala-3/cats/derived/DerivedInvariant.scala b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala index 25fd635f..41b1be98 100644 --- a/core/src/main/scala-3/cats/derived/DerivedInvariant.scala +++ b/core/src/main/scala-3/cats/derived/DerivedInvariant.scala @@ -27,14 +27,6 @@ object DerivedInvariant: given Invariant[G] = G.unify F.unify.compose[G] - given [F[_], G[_]](using N: NotGiven[Or[G]], F: Or[F], G: DerivedFunctor.Or[G]): DerivedInvariant[[x] =>> F[G[x]]] = - given Functor[G] = G.unify - F.unify.composeFunctor[G] - - // given [F[_], G[_]](using N: NotGiven[Or[G]], N1: NotGiven[DerivedFunctor.Or[G]], F: Or[F], G: DerivedContravariant.Or[G]): DerivedInvariant[[x] =>> F[G[x]]] = - // given Contravariant[G] = G.unify - // F.unify.composeContravariant[G] - given [F[_]](using inst: => K1.Instances[Or, F]): DerivedInvariant[F] = given K1.Instances[Invariant, F] = inst.unify new Generic[Invariant, F] {} diff --git a/core/src/test/scala-3/cats/derived/InvariantSuite.scala b/core/src/test/scala-3/cats/derived/InvariantSuite.scala index 9d4e1f85..835285c7 100644 --- a/core/src/test/scala-3/cats/derived/InvariantSuite.scala +++ b/core/src/test/scala-3/cats/derived/InvariantSuite.scala @@ -33,7 +33,7 @@ class InvariantSuite extends KittensSuite: checkAll(s"$context.Invariant[TreeF]", invariantTests[TreeF].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[GenAdtF]", invariantTests[GenericAdtF].invariant[MiniInt, String, Boolean]) // TODO https://github.com/typelevel/kittens/issues/473 - checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean]) + // checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[AndCharF]", invariantTests[AndCharF].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[ListSnoc", invariantTests[ListSnoc].invariant[MiniInt, String, Boolean]) checkAll(s"$context.Invariant[Bivariant]", invariantTests[Bivariant].invariant[MiniInt, String, Boolean]) @@ -78,10 +78,24 @@ object InvariantSuite: object semiInstances: implicit val gadt: Invariant[GenericAdtF] = semiauto.invariant[GenericAdtF] implicit val listSnocendo: Invariant[ListFToInt] = semiauto.invariant[ListFToInt] - implicit val interleaveF: Invariant[InterleavedF] = semiauto.invariant[InterleavedF] + // implicit val interleaveF: Invariant[InterleavedF] = semiauto.invariant[InterleavedF] implicit val andCharF: Invariant[AndCharF] = semiauto.invariant[AndCharF] implicit val treeF: Invariant[TreeF] = semiauto.invariant[TreeF] implicit val pred: Invariant[Pred] = semiauto.invariant[Pred] implicit val snoc: Invariant[ListSnoc] = semiauto.invariant[ListSnoc] implicit val bivariant: Invariant[Bivariant] = semiauto.invariant[Bivariant] implicit val ilist: Invariant[IList] = semiauto.invariant[IList] + + case class Single[A](value: A) derives Invariant + + enum Many[A] derives Invariant: + case Naught() + case More(value: A, rest: Many[A]) + + enum AtMostOne[A] derives Invariant: + case Naught() + case Single(value: A) + + enum AtLeastOne[A] derives Invariant: + case Single(value: A) + case More(value: A, rest: Option[AtLeastOne[A]]) From 8096a8dce409e246c426f45d8724abdba75d500d Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 26 May 2022 10:59:08 +0100 Subject: [PATCH 6/9] Rename partial order derivation --- .../derived/{partialOrder.scala => DerivedPartialOrder.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/src/main/scala-3/cats/derived/{partialOrder.scala => DerivedPartialOrder.scala} (100%) diff --git a/core/src/main/scala-3/cats/derived/partialOrder.scala b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala similarity index 100% rename from core/src/main/scala-3/cats/derived/partialOrder.scala rename to core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala From 03f5cc3bc433a5afe8e005cc9a61bb7673f64009 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 26 May 2022 11:15:36 +0100 Subject: [PATCH 7/9] Port partial order derivation to new scheme --- .../cats/derived/DerivedPartialOrder.scala | 77 +++++++++++-------- .../main/scala-3/cats/derived/package.scala | 7 +- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala index aa82a43a..bfffa94a 100644 --- a/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala +++ b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala @@ -1,34 +1,47 @@ 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] {} +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..850ae45a 100644 --- a/core/src/main/scala-3/cats/derived/package.scala +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -28,8 +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] @@ -53,6 +54,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 +122,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] From af696faa24af9be31d8ac075875908023c8b62d0 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 26 May 2022 11:18:38 +0100 Subject: [PATCH 8/9] Rename to PartialOrderSuite --- .../derived/{PartialOrderTests.scala => PartialOrderSuite.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/src/test/scala-3/cats/derived/{PartialOrderTests.scala => PartialOrderSuite.scala} (100%) diff --git a/core/src/test/scala-3/cats/derived/PartialOrderTests.scala b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala similarity index 100% rename from core/src/test/scala-3/cats/derived/PartialOrderTests.scala rename to core/src/test/scala-3/cats/derived/PartialOrderSuite.scala From 983ac80096f869cd1f9d1b05d98239433255fd86 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 26 May 2022 11:37:22 +0100 Subject: [PATCH 9/9] Port scala 2 partial order suite to scala 3 --- .../cats/derived/DerivedPartialOrder.scala | 3 +- .../cats/derived/PartialOrderSuite.scala | 84 +++++++++++++++++-- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala index bfffa94a..01e730e3 100644 --- a/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala +++ b/core/src/main/scala-3/cats/derived/DerivedPartialOrder.scala @@ -38,8 +38,7 @@ object DerivedPartialOrder: } ) - trait Coproduct[T[x] <: PartialOrder[x], A](using inst: K0.CoproductInstances[T, A]) - extends PartialOrder[A]: + 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)( diff --git a/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala index 33588800..3b0988ae 100644 --- a/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala +++ b/core/src/test/scala-3/cats/derived/PartialOrderSuite.scala @@ -1,9 +1,79 @@ -package cats.derived +/* + * 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. + */ -import alleycats.* -import cats.* -import cats.derived.semiauto.* +package cats +package derived +import cats.kernel.laws.discipline.{PartialOrderTests, SerializableTests} +import org.scalacheck.{Arbitrary, Cogen} +import scala.compiletime.* -class PartialOrderTests { // - case class Foo(i: Int, b: Option[String]) derives PartialOrder -} +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)