diff --git a/core/src/main/scala-3/cats/derived/DerivedContravariant.scala b/core/src/main/scala-3/cats/derived/DerivedContravariant.scala new file mode 100644 index 00000000..b3cec8e8 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/DerivedContravariant.scala @@ -0,0 +1,35 @@ +package cats.derived + +import cats.{Contravariant, Functor} +import shapeless3.deriving.{Const, K1} + +import scala.annotation.implicitNotFound +import scala.compiletime.* + +@implicitNotFound("""Could not derive an instance of Contravariant[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: Functor and H: Contravariant + * it is a generic case class where all fields have a Contravariant instance + * it is a generic sealed trait where all subclasses have a Contravariant instance""") +type DerivedContravariant[F[_]] = Derived[Contravariant[F]] +object DerivedContravariant: + type Or[F[_]] = Derived.Or[Contravariant[F]] + inline def apply[F[_]]: Contravariant[F] = + import DerivedContravariant.given + summonInline[DerivedContravariant[F]].instance + + given [T]: DerivedContravariant[Const[T]] = new Contravariant[Const[T]]: + def contramap[A, B](fa: T)(f: B => A): T = fa + + given [F[_], G[_]](using F: DerivedFunctor.Or[F], G: Or[G]): DerivedContravariant[[x] =>> F[G[x]]] = + given Contravariant[G] = G.unify + F.unify.composeContravariant[G] + + given [F[_]](using inst: => K1.Instances[Or, F]): DerivedContravariant[F] = + given K1.Instances[Contravariant, F] = inst.unify + new Generic[Contravariant, F] {} + + trait Generic[T[x[_]] <: Contravariant[x], F[_]](using inst: K1.Instances[T, F]) extends Contravariant[F]: + final override def contramap[A, B](fa: F[A])(f: B => A): F[B] = + inst.map(fa: F[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.contramap(fa)(f)) diff --git a/core/src/main/scala-3/cats/derived/contravariant.scala b/core/src/main/scala-3/cats/derived/contravariant.scala deleted file mode 100644 index e55af0c3..00000000 --- a/core/src/main/scala-3/cats/derived/contravariant.scala +++ /dev/null @@ -1,15 +0,0 @@ -package cats.derived - -import cats.Contravariant -import shapeless3.deriving.K1 - -trait GenericContravariant[T[x[_]] <: Contravariant[x], F[_]](using inst: K1.Instances[T, F]) extends Contravariant[F]: - - def contramap[A, B](fa: F[A])(f: B => A): F[B] = inst.map(fa)( - [t[_]] => (contra: T[t], t0: t[A]) => contra.contramap(t0)(f) - ) - -trait ContravariantDerivation: - extension (F: Contravariant.type) - inline def derived[F[_]](using gen: K1.Generic[F]): Contravariant[F] = - new GenericContravariant[Contravariant, F] {} diff --git a/core/src/main/scala-3/cats/derived/package.scala b/core/src/main/scala-3/cats/derived/package.scala index 2fa7c556..dd4fcee4 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: Contravariant.type) inline def derived[F[_]]: Contravariant[F] = DerivedContravariant[F] -object semiauto extends ContravariantDerivation, InvariantDerivation, PartialOrderDerivation, Instances: +object semiauto extends InvariantDerivation, 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 contravariant[F[_]]: Contravariant[F] = DerivedContravariant[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 contravariant: + inline given [F[_]](using NotGiven[Contravariant[F]]): Contravariant[F] = DerivedContravariant[F] diff --git a/core/src/test/scala-3/cats/derived/ContravariantSuite.scala b/core/src/test/scala-3/cats/derived/ContravariantSuite.scala new file mode 100644 index 00000000..0817f2ea --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ContravariantSuite.scala @@ -0,0 +1,109 @@ +/* + * 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.laws.discipline.* +import cats.laws.discipline.arbitrary.* +import cats.laws.discipline.eq.* +import scala.compiletime.* + +class ContravariantSuite extends KittensSuite: + import ContravariantSuite.* + import TestDefns.* + + inline def contravariantTests[F[_]]: ContravariantTests[F] = ContravariantTests[F](summonInline) + + inline def testContravariant(context: String): Unit = + checkAll(s"$context.Contravariant[OptPred]", contravariantTests[OptPred].contravariant[MiniInt, String, Boolean]) + checkAll(s"$context.Contravariant[TreePred]", contravariantTests[TreePred].contravariant[MiniInt, String, Boolean]) + checkAll(s"$context.Contravariant[ListPred]", contravariantTests[ListPred].contravariant[MiniInt, String, Boolean]) + checkAll( + s"$context.Contravariant[GenericAdtPred]", + contravariantTests[GenericAdtPred].contravariant[MiniInt, String, Boolean] + ) + // TODO https://github.com/typelevel/kittens/issues/473 + // checkAll( + // s"$context.Contravariant[InterleavedPred]", + // ContravariantTests[InterleavedPred].contravariant[MiniInt, String, Boolean] + // ) + checkAll( + s"$context.Contravariant[AndCharPred]", + contravariantTests[AndCharPred].contravariant[MiniInt, String, Boolean] + ) + checkAll( + s"$context.Contravariant[ListSnocF]", + contravariantTests[ListSnocF].contravariant[MiniInt, String, Boolean] + ) + checkAll( + s"$context.Contravariant is Serializable", + SerializableTests.serializable(summonInline[Contravariant[TreePred]]) + ) + + // TODO https://github.com/typelevel/kittens/issues/476 + // test(s"$context.Contravariant.contramap is stack safe") { + // val C = summonInline[Contravariant[ListSnocF]] + // val n = 10000 + // val largeBoxed = Snoc.fromSeq((1 until n).map((j: Int) => (i: Int) => i + j)) :: Nil + // val actualBoxed = C.contramap[Int, Int](largeBoxed)((j: Int) => j + 1).flatMap(Snoc.toList) + // val expected = (3 until n + 2).toList + // assert(actualBoxed.map(_.apply(1)) == expected) + // } + + locally { + import auto.contravariant.given + testContravariant("auto") + } + + locally { + import semiInstances.given + testContravariant("semiauto") + } + +object ContravariantSuite: + import TestDefns.* + + type OptPred[A] = Option[A => Boolean] + type ListPred[A] = List[A => Boolean] + type GenericAdtPred[A] = GenericAdt[A => Boolean] + type ListSnocF[A] = List[Snoc[A => Int]] + type InterleavedPred[A] = Interleaved[A => Boolean] + type AndCharPred[A] = (A => Boolean, Char) + type TreePred[A] = Tree[A => Boolean] + + object semiInstances: + implicit val optPred: Contravariant[OptPred] = semiauto.contravariant + implicit val treePred: Contravariant[TreePred] = semiauto.contravariant + implicit val listPred: Contravariant[ListPred] = semiauto.contravariant + implicit val genericAdtPred: Contravariant[GenericAdtPred] = semiauto.contravariant + // implicit val interleavePred: Contravariant[InterleavedPred] = semiauto.contravariant + implicit val andCharPred: Contravariant[AndCharPred] = semiauto.contravariant + implicit val listSnocF: Contravariant[ListSnocF] = semiauto.contravariant + + case class Single[A](value: A => Unit) derives Contravariant + + enum Many[-A] derives Contravariant: + case Naught + case More(value: A => Unit, rest: Many[A]) + + enum AtMostOne[-A] derives Contravariant: + case Naught + case Single(value: A => Unit) + + enum AtLeastOne[-A] derives Contravariant: + case Single(value: A => Unit) + case More(value: A => Unit, rest: Option[AtLeastOne[A]]) diff --git a/core/src/test/scala-3/cats/derived/ContravariantTests.scala b/core/src/test/scala-3/cats/derived/ContravariantTests.scala deleted file mode 100644 index 47955456..00000000 --- a/core/src/test/scala-3/cats/derived/ContravariantTests.scala +++ /dev/null @@ -1,10 +0,0 @@ -package cats.derived - -import cats.Contravariant -import cats.derived.semiauto.* - -class ContravariantTests { - - case class Foo[A](f: A => String) derives Contravariant - -} diff --git a/core/src/test/scala-3/cats/derived/MonoidKSuite.scala b/core/src/test/scala-3/cats/derived/MonoidKSuite.scala index 9a0ad5ad..8a605417 100644 --- a/core/src/test/scala-3/cats/derived/MonoidKSuite.scala +++ b/core/src/test/scala-3/cats/derived/MonoidKSuite.scala @@ -7,13 +7,13 @@ import cats.laws.discipline.{MonoidKTests, SerializableTests} import org.scalacheck.Arbitrary import scala.compiletime.* -class MonoidKSuite extends KittensSuite { +class MonoidKSuite extends KittensSuite: import MonoidKSuite.* import TestDefns.* inline def monoidKTests[F[_]]: MonoidKTests[F] = MonoidKTests[F](summonInline) - inline def testMonoidK(context: String): Unit = { + 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]) @@ -24,32 +24,29 @@ class MonoidKSuite extends KittensSuite { assert(M.empty[Char] == Box(Mul[Char](1))) assert(M.combineK(Box(Mul[Char](5)), Box(Mul[Char](5))) == Box(Mul[Char](25))) } - } - { + locally { import auto.monoidK.given testMonoidK("auto") } - { + locally { import monInstances.given testMonoidK("semi") } -} -object MonoidKSuite { +object MonoidKSuite: import TestDefns._ type BoxMul[A] = Box[Mul[A]] - object monInstances { + 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 { + object Mul: implicit def eqv[T]: Eq[Mul[T]] = Eq.by(_.value) @@ -60,8 +57,6 @@ object MonoidKSuite { 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/SemigroupKSuite.scala b/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala index 6214ac03..179e7058 100644 --- a/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala +++ b/core/src/test/scala-3/cats/derived/SemigroupKSuite.scala @@ -5,13 +5,13 @@ import cats.laws.discipline.{SemigroupKTests, SerializableTests} import org.scalacheck.Arbitrary import scala.compiletime.* -class SemigroupKSuite extends KittensSuite { +class SemigroupKSuite extends KittensSuite: import SemigroupKSuite.* import TestDefns.* inline def semigroupKTests[F[_]]: SemigroupKTests[F] = SemigroupKTests[F](summonInline) - inline def testSemigroupK(context: String): Unit = { + 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]) @@ -23,7 +23,6 @@ class SemigroupKSuite extends KittensSuite { 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 @@ -34,21 +33,19 @@ class SemigroupKSuite extends KittensSuite { import semiInstances.given testSemigroupK("semiauto") } -} -object SemigroupKSuite { - import TestDefns._ +object SemigroupKSuite: + import TestDefns.* type BoxMul[A] = Box[Mul[A]] - object semiInstances { + 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 { + object Mul: implicit def eqv[T]: Eq[Mul[T]] = Eq.by(_.value) @@ -58,8 +55,6 @@ object SemigroupKSuite { 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 -}