Skip to content

Commit

Permalink
Port contravariant to new scheme (#475)
Browse files Browse the repository at this point in the history
* Port SemigroupK and MonoidK to new derivation scheme

* WIP port of SemigroupK suite to scala 3

* Nested derivations for SemigroupK

* Priority for derived SemigroupK given instances

* Port scala 2 MonoidK tests to scala 3

* Various improvements

- ImplicitNotFound error for SemigroupK/MonoidK
- Replace given priority via traits with NotGiven

* Use inline in tests

* SemigroupK/MonoidK test for derives syntax

* Port Contravariant to new derivation scheme

* WIP port scala 2 Contravariant tests to scala 3

* Link commented out tests to issues

* derives syntax tests for Contravariant

* derives syntax tests for Contravariant

* Scala 3 import syntax
  • Loading branch information
TimWSpence authored May 25, 2022
1 parent 55bad35 commit 1096075
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 49 deletions.
35 changes: 35 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedContravariant.scala
Original file line number Diff line number Diff line change
@@ -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))
15 changes: 0 additions & 15 deletions core/src/main/scala-3/cats/derived/contravariant.scala

This file was deleted.

7 changes: 6 additions & 1 deletion core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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:
Expand Down Expand Up @@ -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]
109 changes: 109 additions & 0 deletions core/src/test/scala-3/cats/derived/ContravariantSuite.scala
Original file line number Diff line number Diff line change
@@ -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]])
10 changes: 0 additions & 10 deletions core/src/test/scala-3/cats/derived/ContravariantTests.scala

This file was deleted.

19 changes: 7 additions & 12 deletions core/src/test/scala-3/cats/derived/MonoidKSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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)

Expand All @@ -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
}
17 changes: 6 additions & 11 deletions core/src/test/scala-3/cats/derived/SemigroupKSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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
}

0 comments on commit 1096075

Please sign in to comment.