From 1f11a0957f9809b676f092c2f88d1de86fcf18d9 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Sun, 4 Feb 2024 13:38:57 +0200 Subject: [PATCH] Derive Band, Semilattice and BoundedSemilattice on Scala 3 (#645) * Derive Band on Scala 3 * Derive Semilattice on Scala 3 * Derive BoundedSemilattice on Scala 3 --- .../scala-3/cats/derived/DerivedBand.scala | 32 +++++++++ .../derived/DerivedBoundedSemilattice.scala | 34 +++++++++ .../cats/derived/DerivedSemilattice.scala | 34 +++++++++ .../main/scala-3/cats/derived/package.scala | 24 ++++++- core/src/test/scala-3/cats/derived/ADTs.scala | 8 +++ .../test/scala-3/cats/derived/BandSuite.scala | 68 ++++++++++++++++++ .../derived/BoundedSemilatticeSuite.scala | 71 +++++++++++++++++++ .../cats/derived/SemilatticeSuite.scala | 68 ++++++++++++++++++ 8 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala-3/cats/derived/DerivedBand.scala create mode 100644 core/src/main/scala-3/cats/derived/DerivedBoundedSemilattice.scala create mode 100644 core/src/main/scala-3/cats/derived/DerivedSemilattice.scala create mode 100644 core/src/test/scala-3/cats/derived/BandSuite.scala create mode 100644 core/src/test/scala-3/cats/derived/BoundedSemilatticeSuite.scala create mode 100644 core/src/test/scala-3/cats/derived/SemilatticeSuite.scala diff --git a/core/src/main/scala-3/cats/derived/DerivedBand.scala b/core/src/main/scala-3/cats/derived/DerivedBand.scala new file mode 100644 index 00000000..4b23ccc4 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedBand.scala @@ -0,0 +1,32 @@ +package cats.derived + +import cats.kernel.Band +import shapeless3.deriving.K0.* + +import scala.annotation.* +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Band[A] where A = ${A}. +Make sure that A is a case class where all fields have a Band instance.""") +type DerivedBand[A] = Derived[Band[A]] +object DerivedBand: + type Or[A] = Derived.Or[Band[A]] + + @nowarn("msg=unused import") + inline def apply[A]: Band[A] = + import DerivedBand.given + summonInline[DerivedBand[A]].instance + + @nowarn("msg=unused import") + inline def strict[A]: Band[A] = + import Strict.given + summonInline[DerivedBand[A]].instance + + given product[A](using inst: => ProductInstances[Or, A]): DerivedBand[A] = + Strict.product(using inst.unify) + + trait Product[F[x] <: Band[x], A: ProductInstancesOf[F]] extends DerivedSemigroup.Product[F, A], Band[A] + + object Strict: + given product[A: ProductInstancesOf[Band]]: DerivedBand[A] = + new Product[Band, A] {} diff --git a/core/src/main/scala-3/cats/derived/DerivedBoundedSemilattice.scala b/core/src/main/scala-3/cats/derived/DerivedBoundedSemilattice.scala new file mode 100644 index 00000000..fb5a84c0 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedBoundedSemilattice.scala @@ -0,0 +1,34 @@ +package cats.derived + +import cats.kernel.BoundedSemilattice +import shapeless3.deriving.K0.* + +import scala.annotation.* +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of BoundedSemilattice[A] where A = ${A}. +Make sure that A is a case class where all fields have a BoundedSemilattice instance.""") +type DerivedBoundedSemilattice[A] = Derived[BoundedSemilattice[A]] +object DerivedBoundedSemilattice: + type Or[A] = Derived.Or[BoundedSemilattice[A]] + + @nowarn("msg=unused import") + inline def apply[A]: BoundedSemilattice[A] = + import DerivedBoundedSemilattice.given + summonInline[DerivedBoundedSemilattice[A]].instance + + @nowarn("msg=unused import") + inline def strict[A]: BoundedSemilattice[A] = + import Strict.given + summonInline[DerivedBoundedSemilattice[A]].instance + + given product[A](using inst: => ProductInstances[Or, A]): DerivedBoundedSemilattice[A] = + Strict.product(using inst.unify) + + trait Product[F[x] <: BoundedSemilattice[x], A: ProductInstancesOf[F]] + extends DerivedCommutativeMonoid.Product[F, A], + BoundedSemilattice[A] + + object Strict: + given product[A: ProductInstancesOf[BoundedSemilattice]]: DerivedBoundedSemilattice[A] = + new Product[BoundedSemilattice, A] {} diff --git a/core/src/main/scala-3/cats/derived/DerivedSemilattice.scala b/core/src/main/scala-3/cats/derived/DerivedSemilattice.scala new file mode 100644 index 00000000..6df4fe61 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedSemilattice.scala @@ -0,0 +1,34 @@ +package cats.derived + +import cats.kernel.Semilattice +import shapeless3.deriving.K0.* + +import scala.annotation.* +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Semilattice[A] where A = ${A}. +Make sure that A is a case class where all fields have a Semilattice instance.""") +type DerivedSemilattice[A] = Derived[Semilattice[A]] +object DerivedSemilattice: + type Or[A] = Derived.Or[Semilattice[A]] + + @nowarn("msg=unused import") + inline def apply[A]: Semilattice[A] = + import DerivedSemilattice.given + summonInline[DerivedSemilattice[A]].instance + + @nowarn("msg=unused import") + inline def strict[A]: Semilattice[A] = + import Strict.given + summonInline[DerivedSemilattice[A]].instance + + given product[A](using inst: => ProductInstances[Or, A]): DerivedSemilattice[A] = + Strict.product(using inst.unify) + + trait Product[F[x] <: Semilattice[x], A: ProductInstancesOf[F]] + extends DerivedCommutativeSemigroup.Product[F, A], + Semilattice[A] + + object Strict: + given product[A: ProductInstancesOf[Semilattice]]: DerivedSemilattice[A] = + new Product[Semilattice, A] {} diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala index 273c7296..854dfcb3 100644 --- a/core/src/main/scala-3/cats/derived/package.scala +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -2,7 +2,7 @@ package cats.derived import alleycats.* import cats.* -import cats.kernel.{CommutativeGroup, CommutativeMonoid, CommutativeSemigroup} +import cats.kernel.{Band, BoundedSemilattice, CommutativeGroup, CommutativeMonoid, CommutativeSemigroup, Semilattice} import scala.util.NotGiven @@ -12,10 +12,13 @@ extension (x: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A] extension (x: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup[A] extension (x: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid[A] extension (x: Group.type) inline def derived[A]: Group[A] = DerivedGroup[A] +extension (x: Band.type) inline def derived[A]: Band[A] = DerivedBand[A] extension (x: Order.type) inline def derived[A]: Order[A] = DerivedOrder[A] extension (x: CommutativeSemigroup.type) inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A] extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A] extension (x: CommutativeGroup.type) inline def derived[A]: CommutativeGroup[A] = DerivedCommutativeGroup[A] +extension (x: Semilattice.type) inline def derived[A]: Semilattice[A] = DerivedSemilattice[A] +extension (x: BoundedSemilattice.type) inline def derived[A]: BoundedSemilattice[A] = DerivedBoundedSemilattice[A] extension (x: Show.type) inline def derived[A]: Show[A] = DerivedShow[A] extension (x: Applicative.type) inline def derived[F[_]]: Applicative[F] = DerivedApplicative[F] extension (x: Apply.type) inline def derived[F[_]]: Apply[F] = DerivedApply[F] @@ -42,10 +45,13 @@ object semiauto: inline def semigroup[A]: Semigroup[A] = DerivedSemigroup[A] inline def monoid[A]: Monoid[A] = DerivedMonoid[A] inline def group[A]: Group[A] = DerivedGroup[A] + inline def band[A]: Band[A] = DerivedBand[A] inline def order[A]: Order[A] = DerivedOrder[A] inline def commutativeSemigroup[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A] inline def commutativeMonoid[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A] inline def commutativeGroup[A]: CommutativeGroup[A] = DerivedCommutativeGroup[A] + inline def semilattice[A]: Semilattice[A] = DerivedSemilattice[A] + inline def boundedSemilattice[A]: BoundedSemilattice[A] = DerivedBoundedSemilattice[A] inline def applicative[F[_]]: Applicative[F] = DerivedApplicative[F] inline def apply[F[_]]: Apply[F] = DerivedApply[F] inline def nonEmptyAlternative[F[_]]: NonEmptyAlternative[F] = DerivedNonEmptyAlternative[F] @@ -76,10 +82,14 @@ object strict: extension (x: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup.strict[A] extension (x: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid.strict[A] extension (x: Group.type) inline def derived[A]: Group[A] = DerivedGroup.strict[A] + extension (x: Band.type) inline def derived[A]: Band[A] = DerivedBand.strict[A] extension (x: CommutativeSemigroup.type) inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup.strict[A] extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid.strict[A] extension (x: CommutativeGroup.type) inline def derived[A]: CommutativeGroup[A] = DerivedCommutativeGroup.strict[A] + extension (x: Semilattice.type) inline def derived[A]: Semilattice[A] = DerivedSemilattice.strict[A] + extension (x: BoundedSemilattice.type) + inline def derived[A]: BoundedSemilattice[A] = DerivedBoundedSemilattice.strict[A] extension (x: EmptyK.type) inline def derived[F[_]]: EmptyK[F] = DerivedEmptyK.strict[F] extension (x: SemigroupK.type) inline def derived[F[_]]: SemigroupK[F] = DerivedSemigroupK.strict[F] extension (x: MonoidK.type) inline def derived[F[_]]: MonoidK[F] = DerivedMonoidK.strict[F] @@ -108,9 +118,12 @@ object strict: inline def semigroup[A]: Semigroup[A] = DerivedSemigroup.strict[A] inline def monoid[A]: Monoid[A] = DerivedMonoid.strict[A] inline def group[A]: Group[A] = DerivedGroup.strict[A] + inline def band[A]: Band[A] = DerivedBand.strict[A] inline def commutativeSemigroup[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup.strict[A] inline def commutativeMonoid[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid.strict[A] inline def commutativeGroup[A]: CommutativeGroup[A] = DerivedCommutativeGroup.strict[A] + inline def semilattice[A]: Semilattice[A] = DerivedSemilattice.strict[A] + inline def boundedSemilattice[A]: BoundedSemilattice[A] = DerivedBoundedSemilattice.strict[A] inline def emptyK[F[_]]: EmptyK[F] = DerivedEmptyK.strict[F] inline def semigroupK[F[_]]: SemigroupK[F] = DerivedSemigroupK.strict[F] inline def monoidK[F[_]]: MonoidK[F] = DerivedMonoidK.strict[F] @@ -149,6 +162,9 @@ object auto: object group: inline given [A: NotGivenA[Group]]: Group[A] = DerivedGroup[A] + object band: + inline given [A: NotGivenA[Band]]: Band[A] = DerivedBand[A] + object order: inline given [A: NotGivenA[Order]]: Order[A] = DerivedOrder[A] @@ -161,6 +177,12 @@ object auto: object commutativeGroup: inline given [A: NotGivenA[CommutativeGroup]]: CommutativeGroup[A] = DerivedCommutativeGroup[A] + object semilattice: + inline given [A: NotGivenA[Semilattice]]: Semilattice[A] = DerivedSemilattice[A] + + object boundedSemilattice: + inline given [A: NotGivenA[BoundedSemilattice]]: BoundedSemilattice[A] = DerivedBoundedSemilattice[A] + object show: inline given [A: NotGivenA[Show]]: Show[A] = DerivedShow[A] diff --git a/core/src/test/scala-3/cats/derived/ADTs.scala b/core/src/test/scala-3/cats/derived/ADTs.scala index 5d73f44b..e81cd426 100644 --- a/core/src/test/scala-3/cats/derived/ADTs.scala +++ b/core/src/test/scala-3/cats/derived/ADTs.scala @@ -22,6 +22,7 @@ import org.scalacheck.rng.Seed import org.scalacheck.{Arbitrary, Cogen, Gen} import scala.annotation.tailrec +import scala.collection.immutable.BitSet import scala.concurrent.duration.Duration object ADTs: @@ -288,6 +289,13 @@ object ADTs: case class Slice(count: Long, percentile: Double, duration: Duration) case class Compared(x: Slice, y: Slice) + case class Masked[A](mask: BitSet, values: Set[A]) + object Masked: + given [A: Arbitrary]: Arbitrary[Masked[A]] = Arbitrary(for + mask <- Gen.buildableOf[BitSet, Int](Gen.oneOf(Gen.const(0), Gen.posNum[Int])) + values <- Arbitrary.arbitrary[Set[A]] + yield Masked(mask, values)) + trait EqInstances: import ADTs.* diff --git a/core/src/test/scala-3/cats/derived/BandSuite.scala b/core/src/test/scala-3/cats/derived/BandSuite.scala new file mode 100644 index 00000000..d5e25d82 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/BandSuite.scala @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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.derived + +import cats.kernel.Band +import cats.kernel.laws.discipline.{BandTests, SerializableTests} + +import scala.compiletime.* + +class BandSuite extends KittensSuite: + import ADTs.* + import BandSuite.* + + inline def tests[A]: BandTests[A] = + BandTests[A](using summonInline) + + inline def validate(inline instance: String): Unit = + checkAll(s"$instance[Masked[String]]", tests[Masked[String]].band) + checkAll(s"$instance is Serializable", SerializableTests.serializable(summonInline[Band[Masked[String]]])) + + locally: + import auto.band.given + validate("auto.band") + + locally: + import semiInstances.given + validate("semiauto.band") + + locally: + import strictInstances.given + validate("strict.semiauto.band") + testNoInstance("strict.semiauto.band", "Top") + + locally: + import derivedInstances.* + val instance = "derived.band" + checkAll(s"$instance[Masked]]", tests[Masked].band) + checkAll(s"$instance is Serializable", SerializableTests.serializable(Band[Masked])) + +end BandSuite + +object BandSuite: + import ADTs.* + + object semiInstances: + given Band[Masked[String]] = semiauto.band + + object strictInstances: + given Band[Masked[String]] = strict.semiauto.band + + object derivedInstances: + case class Masked(x: ADTs.Masked[String]) derives Band + +end BandSuite diff --git a/core/src/test/scala-3/cats/derived/BoundedSemilatticeSuite.scala b/core/src/test/scala-3/cats/derived/BoundedSemilatticeSuite.scala new file mode 100644 index 00000000..4981738d --- /dev/null +++ b/core/src/test/scala-3/cats/derived/BoundedSemilatticeSuite.scala @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016 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.derived + +import cats.kernel.BoundedSemilattice +import cats.kernel.laws.discipline.{BoundedSemilatticeTests, SerializableTests} + +import scala.compiletime.* + +class BoundedSemilatticeSuite extends KittensSuite: + import ADTs.* + import BoundedSemilatticeSuite.* + + inline def tests[A]: BoundedSemilatticeTests[A] = + BoundedSemilatticeTests[A](using summonInline) + + inline def validate(inline instance: String): Unit = + checkAll(s"$instance[Masked[String]]", tests[Masked[String]].boundedSemilattice) + checkAll( + s"$instance is Serializable", + SerializableTests.serializable(summonInline[BoundedSemilattice[Masked[String]]]) + ) + + locally: + import auto.boundedSemilattice.given + validate("auto.boundedSemilattice") + + locally: + import semiInstances.given + validate("semiauto.boundedSemilattice") + + locally: + import strictInstances.given + validate("strict.semiauto.boundedSemilattice") + testNoInstance("strict.semiauto.boundedSemilattice", "Top") + + locally: + import derivedInstances.* + val instance = "derived.boundedSemilattice" + checkAll(s"$instance[Masked]]", tests[Masked].boundedSemilattice) + checkAll(s"$instance is Serializable", SerializableTests.serializable(BoundedSemilattice[Masked])) + +end BoundedSemilatticeSuite + +object BoundedSemilatticeSuite: + import ADTs.* + + object semiInstances: + given BoundedSemilattice[Masked[String]] = semiauto.boundedSemilattice + + object strictInstances: + given BoundedSemilattice[Masked[String]] = strict.semiauto.boundedSemilattice + + object derivedInstances: + case class Masked(x: ADTs.Masked[String]) derives BoundedSemilattice + +end BoundedSemilatticeSuite diff --git a/core/src/test/scala-3/cats/derived/SemilatticeSuite.scala b/core/src/test/scala-3/cats/derived/SemilatticeSuite.scala new file mode 100644 index 00000000..a5f717a2 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/SemilatticeSuite.scala @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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.derived + +import cats.kernel.Semilattice +import cats.kernel.laws.discipline.{SemilatticeTests, SerializableTests} + +import scala.compiletime.* + +class SemilatticeSuite extends KittensSuite: + import ADTs.* + import SemilatticeSuite.* + + inline def tests[A]: SemilatticeTests[A] = + SemilatticeTests[A](using summonInline) + + inline def validate(inline instance: String): Unit = + checkAll(s"$instance[Masked[String]]", tests[Masked[String]].semilattice) + checkAll(s"$instance is Serializable", SerializableTests.serializable(summonInline[Semilattice[Masked[String]]])) + + locally: + import auto.semilattice.given + validate("auto.semilattice") + + locally: + import semiInstances.given + validate("semiauto.semilattice") + + locally: + import strictInstances.given + validate("strict.semiauto.semilattice") + testNoInstance("strict.semiauto.semilattice", "Top") + + locally: + import derivedInstances.* + val instance = "derived.semilattice" + checkAll(s"$instance[Masked]]", tests[Masked].semilattice) + checkAll(s"$instance is Serializable", SerializableTests.serializable(Semilattice[Masked])) + +end SemilatticeSuite + +object SemilatticeSuite: + import ADTs.* + + object semiInstances: + given Semilattice[Masked[String]] = semiauto.semilattice + + object strictInstances: + given Semilattice[Masked[String]] = strict.semiauto.semilattice + + object derivedInstances: + case class Masked(x: ADTs.Masked[String]) derives Semilattice + +end SemilatticeSuite