diff --git a/core/src/main/scala-3/cats/derived/DerivedMonoidK.scala b/core/src/main/scala-3/cats/derived/DerivedMonoidK.scala new file mode 100644 index 00000000..69cc75d0 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedMonoidK.scala @@ -0,0 +1,51 @@ +package cats.derived + +import cats.* +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.util.NotGiven + +@implicitNotFound("""Could not derive an instance of MonoidK[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Monoid + * it is a nested type [x] =>> G[H[x]] where G: MonoidK + * it is a nested type [x] =>> G[H[x]] where G: Applicative and H: MonoidK + * it is a generic case class where all fields have a MonoidK instance""") +type DerivedMonoidK[F[_]] = Derived[MonoidK[F]] +object DerivedMonoidK: + type Or[F[_]] = Derived.Or[MonoidK[F]] + inline def apply[F[_]]: MonoidK[F] = + import DerivedMonoidK.given + summonInline[DerivedMonoidK[F]].instance + + given [T](using T: Monoid[T]): DerivedMonoidK[Const[T]] = new MonoidK[Const[T]]: + final override def empty[A]: Const[T][A] = T.empty + + final override def combineK[A](x: Const[T][A], y: Const[T][A]) = T.combine(x, y) + + given [F[_], G[_]](using F: Or[F]): DerivedMonoidK[[x] =>> F[G[x]]] = + F.unify.compose[G] + + given [F[_], G[_]](using + N: NotGiven[Or[F]], + F0: DerivedApplicative.Or[F], + G0: Or[G] + ): DerivedMonoidK[[x] =>> F[G[x]]] = + new MonoidK[[x] =>> F[G[x]]]: + val F: Applicative[F] = F0.unify + val G: MonoidK[G] = G0.unify + + final override def empty[A]: F[G[A]] = F.pure(G.empty[A]) + + final override def combineK[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.map2(x, y)(G.combineK(_, _)) + + given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedMonoidK[F] = + given K1.ProductInstances[MonoidK, F] = inst.unify + new Product[MonoidK, F] {} + + trait Product[T[x[_]] <: MonoidK[x], F[_]](using inst: K1.ProductInstances[T, F]) + extends MonoidK[F], + DerivedSemigroupK.Product[T, F]: + final override def empty[A]: F[A] = inst.construct([t[_]] => (emp: T[t]) => emp.empty[A]) diff --git a/core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala b/core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala new file mode 100644 index 00000000..d3099cfb --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala @@ -0,0 +1,39 @@ +package cats.derived + +import cats.{Semigroup, SemigroupK} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* +import scala.util.NotGiven + +@implicitNotFound("""Could not derive an instance of SemigroupK[F] where F = ${F}. +Make sure that F[_] satisfies one of the following conditions: + * it is a constant type [x] =>> T where T: Semigroup + * it is a nested type [x] =>> G[H[x]] where G: SemigroupK + * it is a nested type [x] =>> G[H[x]] where G: Apply and H: SemigroupK + * it is a generic case class where all fields have a SemigroupK instance""") +type DerivedSemigroupK[F[_]] = Derived[SemigroupK[F]] +object DerivedSemigroupK: + type Or[F[_]] = Derived.Or[SemigroupK[F]] + inline def apply[F[_]]: SemigroupK[F] = + import DerivedSemigroupK.given + summonInline[DerivedSemigroupK[F]].instance + + given [T](using T: Semigroup[T]): DerivedSemigroupK[Const[T]] = new SemigroupK[Const[T]]: + final override def combineK[A](x: Const[T][A], y: Const[T][A]) = T.combine(x, y) + + given [F[_], G[_]](using F: Or[F]): DerivedSemigroupK[[x] =>> F[G[x]]] = + F.unify.compose[G] + + given [F[_], G[_]](using N: NotGiven[Or[F]], F: DerivedApply.Or[F], G: Or[G]): DerivedSemigroupK[[x] =>> F[G[x]]] = + new SemigroupK[[x] =>> F[G[x]]]: + final override def combineK[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.unify.map2(x, y)(G.unify.combineK(_, _)) + + given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedSemigroupK[F] = + given K1.ProductInstances[SemigroupK, F] = inst.unify + new Product[SemigroupK, F] {} + + trait Product[T[x[_]] <: SemigroupK[x], F[_]](using inst: K1.ProductInstances[T, F]) extends SemigroupK[F]: + final override def combineK[A](x: F[A], y: F[A]): F[A] = + inst.map2[A, A, A](x, y)([t[_]] => (smgrpk: T[t], x: t[A], y: t[A]) => smgrpk.combineK(x, y)) diff --git a/core/src/main/scala-3/cats/derived/monoidk.scala b/core/src/main/scala-3/cats/derived/monoidk.scala deleted file mode 100644 index cc5e878c..00000000 --- a/core/src/main/scala-3/cats/derived/monoidk.scala +++ /dev/null @@ -1,12 +0,0 @@ -package cats.derived - -import cats.{Monoid, MonoidK} -import shapeless3.deriving.{Const, K1} - -trait ProductMonoidK[T[x[_]] <: MonoidK[x], F[_]](using inst: K1.ProductInstances[T, F]) - extends ProductSemigroupK[T, F], - MonoidK[F]: - def empty[A]: F[A] = inst.construct([t[_]] => (emp: T[t]) => emp.empty[A]) - -trait MonoidKDerivation: - extension (F: MonoidK.type) inline def derived[F[_]](using gen: K1.Generic[F]): MonoidK[F] = ??? diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala index a3add49d..2fa7c556 100644 --- a/core/src/main/scala-3/cats/derived/package.scala +++ b/core/src/main/scala-3/cats/derived/package.scala @@ -24,14 +24,10 @@ extension (x: Functor.type) inline def derived[F[_]]: Functor[F] = DerivedFuncto extension (x: Reducible.type) inline def derived[F[_]]: Reducible[F] = DerivedReducible[F] extension (x: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTraverse[F] 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] -object semiauto - extends ContravariantDerivation, - InvariantDerivation, - MonoidKDerivation, - PartialOrderDerivation, - SemigroupKDerivation, - Instances: +object semiauto extends ContravariantDerivation, InvariantDerivation, PartialOrderDerivation, Instances: inline def eq[A]: Eq[A] = DerivedEq[A] inline def hash[A]: Hash[A] = DerivedHash[A] @@ -51,6 +47,8 @@ object semiauto inline def traverse[F[_]]: Traverse[F] = DerivedTraverse[F] inline def nonEmptyTraverse[F[_]]: NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F] inline def show[A]: Show[A] = DerivedShow[A] + inline def semigroupK[F[_]]: SemigroupK[F] = DerivedSemigroupK[F] + inline def monoidK[F[_]]: MonoidK[F] = DerivedMonoidK[F] object auto: object eq: @@ -106,3 +104,9 @@ object auto: object nonEmptyTraverse: inline given [F[_]](using NotGiven[NonEmptyTraverse[F]]): NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F] + + object semigroupK: + inline given [F[_]](using NotGiven[SemigroupK[F]]): SemigroupK[F] = DerivedSemigroupK[F] + + object monoidK: + inline given [F[_]](using NotGiven[MonoidK[F]]): MonoidK[F] = DerivedMonoidK[F] diff --git a/core/src/main/scala-3/cats/derived/semigroupk.scala b/core/src/main/scala-3/cats/derived/semigroupk.scala deleted file mode 100644 index c9b000f3..00000000 --- a/core/src/main/scala-3/cats/derived/semigroupk.scala +++ /dev/null @@ -1,14 +0,0 @@ -package cats.derived - -import cats.{Semigroup, SemigroupK} -import shapeless3.deriving.{Const, K1} - -trait ProductSemigroupK[T[x[_]] <: SemigroupK[x], F[_]](using inst: K1.ProductInstances[T, F]) extends SemigroupK[F]: - def combineK[A](x: F[A], y: F[A]): F[A] = inst.map2[A, A, A](x, y)( - [t[_]] => (smgrpk: T[t], t0: t[A], t1: t[A]) => smgrpk.combineK(t0, t1) - ) - -trait SemigroupKDerivation: - extension (F: SemigroupK.type) - inline def derived[F[_]](using gen: K1.ProductGeneric[F]): SemigroupK[F] = - new ProductSemigroupK[SemigroupK, F] {} diff --git a/core/src/test/scala-3/cats/derived/MonoidKSuite.scala b/core/src/test/scala-3/cats/derived/MonoidKSuite.scala new file mode 100644 index 00000000..9a0ad5ad --- /dev/null +++ b/core/src/test/scala-3/cats/derived/MonoidKSuite.scala @@ -0,0 +1,67 @@ +package cats.derived + +import alleycats.* +import cats.* +import cats.derived.* +import cats.laws.discipline.{MonoidKTests, SerializableTests} +import org.scalacheck.Arbitrary +import scala.compiletime.* + +class MonoidKSuite extends KittensSuite { + import MonoidKSuite.* + import TestDefns.* + + inline def monoidKTests[F[_]]: MonoidKTests[F] = MonoidKTests[F](summonInline) + + inline def testMonoidK(context: String): Unit = { + checkAll(s"$context.MonoidK[ComplexProduct]", monoidKTests[ComplexProduct].monoidK[Char]) + checkAll(s"$context.MonoidK[CaseClassWOption]", monoidKTests[CaseClassWOption].monoidK[Char]) + checkAll(s"$context.MonoidK[BoxMul]", monoidKTests[BoxMul].monoidK[Char]) + checkAll(s"$context.MonoidK is Serializable", SerializableTests.serializable(summonInline[MonoidK[ComplexProduct]])) + + test(s"$context.MonoidK respects existing instances") { + val M = summonInline[MonoidK[BoxMul]] + assert(M.empty[Char] == Box(Mul[Char](1))) + assert(M.combineK(Box(Mul[Char](5)), Box(Mul[Char](5))) == Box(Mul[Char](25))) + } + } + + { + import auto.monoidK.given + testMonoidK("auto") + } + + { + import monInstances.given + testMonoidK("semi") + } +} + +object MonoidKSuite { + import TestDefns._ + + type BoxMul[A] = Box[Mul[A]] + + object monInstances { + implicit val complexProduct: MonoidK[ComplexProduct] = semiauto.monoidK + implicit val caseClassWOption: MonoidK[CaseClassWOption] = semiauto.monoidK + implicit val boxMul: MonoidK[BoxMul] = semiauto.monoidK + } + + final case class Mul[T](value: Int) + object Mul { + + implicit def eqv[T]: Eq[Mul[T]] = Eq.by(_.value) + + implicit def arbitrary[T]: Arbitrary[Mul[T]] = + Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + + implicit val monoidK: MonoidK[Mul] = new MonoidK[Mul] { + def empty[A] = Mul(1) + def combineK[A](x: Mul[A], y: Mul[A]) = Mul(x.value * y.value) + } + } + + case class Simple[A](value1: List[A], value2: Set[A]) derives MonoidK + case class Recursive[A](first: List[A], rest: Recursive[A]) derives MonoidK +} diff --git a/core/src/test/scala-3/cats/derived/MonoidKTests.scala b/core/src/test/scala-3/cats/derived/MonoidKTests.scala deleted file mode 100644 index 80028a5d..00000000 --- a/core/src/test/scala-3/cats/derived/MonoidKTests.scala +++ /dev/null @@ -1,9 +0,0 @@ -package cats.derived - -import alleycats.* -import cats.* -import cats.derived.semiauto.* - -class MonoidKTests { // - case class Foo[A](i: String, l: List[A]) derives MonoidK -} diff --git a/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala b/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala new file mode 100644 index 00000000..6214ac03 --- /dev/null +++ b/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala @@ -0,0 +1,65 @@ +package cats.derived + +import cats.* +import cats.laws.discipline.{SemigroupKTests, SerializableTests} +import org.scalacheck.Arbitrary +import scala.compiletime.* + +class SemigroupKSuite extends KittensSuite { + import SemigroupKSuite.* + import TestDefns.* + + inline def semigroupKTests[F[_]]: SemigroupKTests[F] = SemigroupKTests[F](summonInline) + + inline def testSemigroupK(context: String): Unit = { + checkAll(s"$context.SemigroupK[ComplexProduct]", semigroupKTests[ComplexProduct].semigroupK[Char]) + checkAll(s"$context.SemigroupK[CaseClassWOption]", semigroupKTests[CaseClassWOption].semigroupK[Char]) + checkAll(s"$context.SemigroupK[BoxMul]", semigroupKTests[BoxMul].semigroupK[Char]) + checkAll( + s"$context.SemigroupK is Serializable", + SerializableTests.serializable(summonInline[SemigroupK[ComplexProduct]]) + ) + + test(s"$context.SemigroupK respects existing instances") { + assert(summonInline[SemigroupK[BoxMul]].combineK(Box(Mul[Char](5)), Box(Mul[Char](5))) == Box(Mul[Char](25))) + } + } + + locally { + import auto.semigroupK.given + testSemigroupK("auto") + } + + locally { + import semiInstances.given + testSemigroupK("semiauto") + } +} + +object SemigroupKSuite { + import TestDefns._ + + type BoxMul[A] = Box[Mul[A]] + + object semiInstances { + implicit val complexProduct: SemigroupK[ComplexProduct] = semiauto.semigroupK + implicit val caseClassWOption: SemigroupK[CaseClassWOption] = semiauto.semigroupK + implicit val boxMul: SemigroupK[BoxMul] = semiauto.semigroupK + } + + final case class Mul[T](value: Int) + object Mul { + + implicit def eqv[T]: Eq[Mul[T]] = Eq.by(_.value) + + implicit def arbitrary[T]: Arbitrary[Mul[T]] = + Arbitrary(Arbitrary.arbitrary[Int].map(apply)) + + implicit val semigroupK: SemigroupK[Mul] = new SemigroupK[Mul] { + def combineK[A](x: Mul[A], y: Mul[A]) = Mul(x.value * y.value) + } + } + + case class Simple[A](value1: List[A], value2: Set[A]) derives SemigroupK + case class Recursive[A](first: List[A], rest: Recursive[A]) derives SemigroupK +} diff --git a/core/src/test/scala-3/cats/derived/SemigroupKTests.scala b/core/src/test/scala-3/cats/derived/SemigroupKTests.scala deleted file mode 100644 index e5030765..00000000 --- a/core/src/test/scala-3/cats/derived/SemigroupKTests.scala +++ /dev/null @@ -1,10 +0,0 @@ -package cats.derived - -import alleycats.* -import cats.* -import cats.derived.semiauto.* -import cats.derived.semiauto.given - -class SemigroupKTests { // - case class Foo[A](i: String, l: List[A]) derives SemigroupK -}