Skip to content

Commit

Permalink
Port Semigroupk/Monoidk to new scheme (#472)
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
  • Loading branch information
TimWSpence authored May 24, 2022
1 parent 8f76c24 commit 55bad35
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 52 deletions.
51 changes: 51 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedMonoidK.scala
Original file line number Diff line number Diff line change
@@ -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])
39 changes: 39 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedSemigroupK.scala
Original file line number Diff line number Diff line change
@@ -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))
12 changes: 0 additions & 12 deletions core/src/main/scala-3/cats/derived/monoidk.scala

This file was deleted.

18 changes: 11 additions & 7 deletions core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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:
Expand Down Expand Up @@ -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]
14 changes: 0 additions & 14 deletions core/src/main/scala-3/cats/derived/semigroupk.scala

This file was deleted.

67 changes: 67 additions & 0 deletions core/src/test/scala-3/cats/derived/MonoidKSuite.scala
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 0 additions & 9 deletions core/src/test/scala-3/cats/derived/MonoidKTests.scala

This file was deleted.

65 changes: 65 additions & 0 deletions core/src/test/scala-3/cats/derived/SemigroupKSuite.scala
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 0 additions & 10 deletions core/src/test/scala-3/cats/derived/SemigroupKTests.scala

This file was deleted.

0 comments on commit 55bad35

Please sign in to comment.