Skip to content

Commit

Permalink
Strict semiauto derivation for Scala 3 (#626)
Browse files Browse the repository at this point in the history
* Strict semiauto derivation for Scala 3

* Strict derivation for Hash

* Strict derivation for Empty

* Strict derivation for Semigroup

* Strict derivation for Monoid

* Strict derivation for Order

* Strict derivation for CommutativeSemigroup

* Strict derivation for CommutativeMonoid

* Deprecate nameless givens

* Strict derivation for Show

* Strict derivation for PartialOrder

* Strict derivation for ShowPretty

* Strict derivation for EmptyK

* Strict derivation for SemigroupK

* Strict derivation for MonoidK

* Strict derivation for Pure

* Strict derivation for Invariant

* Reduce number of deprecated methods

* Strict derivation for Functor

* Strict derivation for Contravariant

* Strict derivation for Foldable

* Strict validation for Apply

* Strict derivation for Applicative

* Strict derivation for Reducible

* Strict derivation for Traverse

* Strict derivation for NonEmptyTraverse

* Restrict strict derivation to product and coproduct
  • Loading branch information
joroKr21 authored Jan 7, 2024
1 parent 188446b commit 076ffce
Show file tree
Hide file tree
Showing 52 changed files with 952 additions and 251 deletions.
5 changes: 2 additions & 3 deletions core/src/main/scala-3/cats/derived/Derived.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ object Derived:
extension [I[f[_[_, _]], t[_, _]] <: K2.Instances[f, t], F[_[_, _]], T[_, _]](inst: I[Or2[F], T])
@targetName("unifyK2") def unify: I[F, T] = inst

abstract private[derived] class Lazy[A](f: () => A) extends Serializable:
private[derived] class Lazy[A](f: () => A) extends Serializable:
final protected lazy val delegate: A = f()

sealed abstract class OrInstances:
inline given [A]: Derived.Or[A] = summonFrom {
inline given [A]: Derived.Or[A] = summonFrom:
case instance: A => Derived.Or(instance)
case derived: Derived[A] => Derived.Or(derived.instance)
}
20 changes: 15 additions & 5 deletions core/src/main/scala-3/cats/derived/DerivedApplicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,32 @@ object DerivedApplicative:
import DerivedApplicative.given
summonInline[DerivedApplicative[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: Applicative[F] =
import Strict.given
summonInline[DerivedApplicative[F]].instance

given [T](using T: Monoid[T]): DerivedApplicative[Const[T]] = new Applicative[Const[T]]:
def pure[A](x: A): T = T.empty
def ap[A, B](ff: T)(fa: T): T = T.combine(ff, fa)

given nested[F[_], G[_]](using F: => Or[F], G: => Or[G]): DerivedApplicative[[x] =>> F[G[x]]] =
new Derived.Lazy(() => F.unify.compose(G.unify)) with Applicative[[x] =>> F[G[x]]]:
new Derived.Lazy(() => F.unify.compose(using G.unify)) with Applicative[[x] =>> F[G[x]]]:
export delegate.*

given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedApplicative[F] =
given K1.ProductInstances[Applicative, F] = inst.unify
new Product[Applicative, F] with DerivedApply.Product[Applicative, F] {}
Strict.product(using inst.unify)

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedApplicative_F[F[_]: Or, G[_]: Or]: DerivedApplicative[[x] =>> F[G[x]]] = summon
protected given [F[_]: Or, G[_]: Or]: DerivedApplicative[[x] =>> F[G[x]]] = nested

trait Product[T[f[_]] <: Applicative[f], F[_]](using inst: K1.ProductInstances[T, F])
extends Applicative[F],
DerivedApply.Product[T, F]:
final override def pure[A](x: A): F[A] = inst.construct([f[_]] => (F: T[f]) => F.pure[A](x))

final override def pure[A](x: A): F[A] =
inst.construct([f[_]] => (F: T[f]) => F.pure[A](x))

object Strict:
given product[F[_]](using K1.ProductInstances[Applicative, F]): DerivedApplicative[F] =
new Product[Applicative, F] with DerivedApply.Product[Applicative, F] {}
16 changes: 12 additions & 4 deletions core/src/main/scala-3/cats/derived/DerivedApply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,31 @@ object DerivedApply:
import DerivedApply.given
summonInline[DerivedApply[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: Apply[F] =
import Strict.given
summonInline[DerivedApply[F]].instance

given [T](using T: Semigroup[T]): DerivedApply[Const[T]] = new Apply[Const[T]]:
def ap[A, B](ff: T)(fa: T): T = T.combine(ff, fa)
def map[A, B](fa: T)(f: A => B): T = fa

given nested[F[_], G[_]](using F: => Or[F], G: => Or[G]): DerivedApply[[x] =>> F[G[x]]] =
new Derived.Lazy(() => F.unify.compose(G.unify)) with Apply[[x] =>> F[G[x]]]:
new Derived.Lazy(() => F.unify.compose(using G.unify)) with Apply[[x] =>> F[G[x]]]:
export delegate.*

given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedApply[F] =
given K1.ProductInstances[Apply, F] = inst.unify
new Product[Apply, F] {}
Strict.product(using inst.unify)

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedApply_F[F[_]: Or, G[_]: Or]: DerivedApply[[x] =>> F[G[x]]] = summon
protected given [F[_]: Or, G[_]: Or]: DerivedApply[[x] =>> F[G[x]]] = nested

trait Product[T[f[_]] <: Apply[f], F[_]](using inst: K1.ProductInstances[T, F]) extends Apply[F]:
private lazy val F = new DerivedFunctor.Generic[T, F] {}
final override def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
final override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] =
inst.map2(ff, fa)([f[_]] => (F: T[f], ff: f[A => B], fa: f[A]) => F.ap(ff)(fa))

object Strict:
given product[F[_]](using K1.ProductInstances[Apply, F]): DerivedApply[F] =
new Product[Apply, F] {}
12 changes: 10 additions & 2 deletions core/src/main/scala-3/cats/derived/DerivedCommutativeMonoid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ object DerivedCommutativeMonoid:
import DerivedCommutativeMonoid.given
summonInline[DerivedCommutativeMonoid[A]].instance

@nowarn("msg=unused import")
inline def strict[A]: CommutativeMonoid[A] =
import Strict.given
summonInline[DerivedCommutativeMonoid[A]].instance

given [A](using inst: => K0.ProductInstances[Or, A]): DerivedCommutativeMonoid[A] =
given K0.ProductInstances[CommutativeMonoid, A] = inst.unify
new Product[CommutativeMonoid, A] {}
Strict.product(using inst.unify)

trait Product[F[x] <: CommutativeMonoid[x], A](using @unused inst: K0.ProductInstances[F, A])
extends DerivedMonoid.Product[F, A],
CommutativeMonoid[A]

object Strict:
given product[A](using K0.ProductInstances[CommutativeMonoid, A]): DerivedCommutativeMonoid[A] =
new Product[CommutativeMonoid, A] {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ object DerivedCommutativeSemigroup:
import DerivedCommutativeSemigroup.given
summonInline[DerivedCommutativeSemigroup[A]].instance

@nowarn("msg=unused import")
inline def strict[A]: CommutativeSemigroup[A] =
import Strict.given
summonInline[DerivedCommutativeSemigroup[A]].instance

given [A](using inst: => K0.ProductInstances[Or, A]): DerivedCommutativeSemigroup[A] =
given K0.ProductInstances[CommutativeSemigroup, A] = inst.unify
new Product[CommutativeSemigroup, A] {}
Strict.product(using inst.unify)

trait Product[F[x] <: CommutativeSemigroup[x], A](using @unused inst: K0.ProductInstances[F, A])
extends DerivedSemigroup.Product[F, A],
CommutativeSemigroup[A]

object Strict:
given product[A](using K0.ProductInstances[CommutativeSemigroup, A]): DerivedCommutativeSemigroup[A] =
new Product[CommutativeSemigroup, A] {}
21 changes: 16 additions & 5 deletions core/src/main/scala-3/cats/derived/DerivedContravariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,32 @@ object DerivedContravariant:
import DerivedContravariant.given
summonInline[DerivedContravariant[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: Contravariant[F] =
import Strict.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 nested[F[_], G[_]](using F: DerivedFunctor.Or[F], G: => Or[G]): DerivedContravariant[[x] =>> F[G[x]]] =
new Derived.Lazy(() => F.unify.composeContravariant(G.unify)) with Contravariant[[x] =>> F[G[x]]]:
new Derived.Lazy(() => F.unify.composeContravariant(using G.unify)) with Contravariant[[x] =>> F[G[x]]]:
export delegate.*

given [F[_]](using inst: => K1.Instances[Or, F]): DerivedContravariant[F] =
given K1.Instances[Contravariant, F] = inst.unify
new Generic[Contravariant, F] {}
generic(using inst.unify)

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedContravariant_F[F[_]: DerivedFunctor.Or, G[_]: Or]
: DerivedContravariant[[x] =>> F[G[x]]] = summon
protected given [F[_]: DerivedFunctor.Or, G[_]: Or]: DerivedContravariant[[x] =>> F[G[x]]] = nested

private def generic[F[_]](using K1.Instances[Contravariant, F]): DerivedContravariant[F] =
new Generic[Contravariant, F] {}

trait Generic[T[f[_]] <: Contravariant[f], 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[_]] => (T: T[f], fa: f[A]) => T.contramap(fa)(f))

object Strict:
given product[F[_]](using K1.ProductInstances[Contravariant, F]): DerivedContravariant[F] = generic
given coproduct[F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedContravariant[F] =
generic(using inst.unify)
17 changes: 13 additions & 4 deletions core/src/main/scala-3/cats/derived/DerivedEmpty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ object DerivedEmpty:
import DerivedEmpty.given
summonInline[DerivedEmpty[A]].instance

given product[A](using inst: K0.ProductInstances[Or, A]): DerivedEmpty[A] =
Empty(inst.unify.construct([a] => (_: Empty[a]).empty))
@nowarn("msg=unused import")
inline def strict[A]: Empty[A] =
import Strict.given
summonInline[DerivedEmpty[A]].instance

given product[A](using inst: K0.ProductInstances[Or, A]): DerivedEmpty[A] = Strict.product(using inst.unify)
inline given coproduct[A](using gen: K0.CoproductGeneric[A]): DerivedEmpty[A] = Strict.coproduct

object Strict:
given product[A](using inst: K0.ProductInstances[Empty, A]): DerivedEmpty[A] =
Empty(inst.construct([a] => (A: Empty[a]) => A.empty))

inline given coproduct[A](using gen: K0.CoproductGeneric[A]): DerivedEmpty[A] =
Empty(gen.withOnly[Or, A]([a <: A] => (_: Or[a]).unify.empty))
inline given coproduct[A](using gen: K0.CoproductGeneric[A]): DerivedEmpty[A] =
Empty(gen.withOnly[Or, A]([a <: A] => (A: Or[a]) => A.unify.empty))
49 changes: 30 additions & 19 deletions core/src/main/scala-3/cats/derived/DerivedEmptyK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,43 @@ object DerivedEmptyK:
import DerivedEmptyK.given
summonInline[DerivedEmptyK[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: EmptyK[F] =
import Strict.given
summonInline[DerivedEmptyK[F]].instance

given [T](using T: Empty[T]): DerivedEmptyK[Const[T]] = new EmptyK[Const[T]]:
def empty[A]: T = T.empty

given nested[F[_], G[_]](using F: => Or[F]): DerivedEmptyK[[x] =>> F[G[x]]] = new EmptyK[[x] =>> F[G[x]]]:
lazy val f = F.unify
def empty[A]: F[G[A]] = f.empty
given nested[F[_], G[_]](using F: => Or[F]): DerivedEmptyK[[x] =>> F[G[x]]] =
new EmptyK[[x] =>> F[G[x]]]:
lazy val f = F.unify
def empty[A]: F[G[A]] = f.empty

given nested[F[_], G[_]](using NotGiven[Or[F]])(using
F: DerivedPure.Or[F],
G: => Or[G]
): DerivedEmptyK[[x] =>> F[G[x]]] = new EmptyK[[x] =>> F[G[x]]]:
val f = F.unify
lazy val g = G.unify
def empty[A]: F[G[A]] = f.pure(g.empty)
given nested[F[_], G[_]](using
NotGiven[Or[F]]
)(using F: DerivedPure.Or[F], G: => Or[G]): DerivedEmptyK[[x] =>> F[G[x]]] =
new EmptyK[[x] =>> F[G[x]]]:
val f = F.unify
lazy val g = G.unify
def empty[A]: F[G[A]] = f.pure(g.empty)

given product[F[_]](using inst: K1.ProductInstances[Or, F]): DerivedEmptyK[F] = new EmptyK[F]:
val f = inst.unify
def empty[A]: F[A] = f.construct([f[_]] => (F: EmptyK[f]) => F.empty[A])

inline given coproduct[F[_]](using gen: K1.CoproductGeneric[F]): DerivedEmptyK[F] =
gen.withOnly[Or, EmptyK[F]]([f[x] <: F[x]] => (F: Or[f]) => F.unify.asInstanceOf[EmptyK[F]])
given product[F[_]](using inst: K1.ProductInstances[Or, F]): DerivedEmptyK[F] = Strict.product(using inst.unify)
inline given coproduct[F[_]](using K1.CoproductGeneric[F]): DerivedEmptyK[F] = Strict.coproduct

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedEmptyK_F[F[_]: Or, G[_]]: DerivedEmptyK[[x] =>> F[G[x]]] = summon
protected given [F[_], G[_]](using F: Or[F]): DerivedEmptyK[[x] =>> F[G[x]]] =
nested(using F)

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedEmptyK_F[F[_]: DerivedPure.Or, G[_]: Or](
protected given [F[_], G[_]](using
ev: NotGiven[Or[F]]
): DerivedEmptyK[[x] =>> F[G[x]]] = nested(using ev)
)(using DerivedPure.Or[F], Or[G]): DerivedEmptyK[[x] =>> F[G[x]]] =
nested(using ev)

object Strict:
given product[F[_]](using inst: K1.ProductInstances[EmptyK, F]): DerivedEmptyK[F] = new EmptyK[F]:
def empty[A]: F[A] = inst.construct([f[_]] => (F: EmptyK[f]) => F.empty[A])

inline given coproduct[F[_]](using gen: K1.CoproductGeneric[F]): DerivedEmptyK[F] =
gen.withOnly[Or, EmptyK[F]]([f[x] <: F[x]] => (F: Or[f]) => F.unify.asInstanceOf[EmptyK[F]])
13 changes: 11 additions & 2 deletions core/src/main/scala-3/cats/derived/DerivedEq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ object DerivedEq:
import DerivedEq.given
summonInline[DerivedEq[A]].instance

@nowarn("msg=unused import")
inline def strict[A]: Eq[A] =
import Strict.given
summonInline[DerivedEq[A]].instance

given singleton[A <: Singleton: ValueOf]: DerivedEq[A] =
Eq.allEqual

given product[A](using inst: => K0.ProductInstances[Or, A]): DerivedEq[A] =
given K0.ProductInstances[Eq, A] = inst.unify
new Product[Eq, A] {}
Strict.product(using inst.unify)

given coproduct[A](using inst: => K0.CoproductInstances[Or, A]): DerivedEq[A] =
given K0.CoproductInstances[Eq, A] = inst.unify
Expand All @@ -37,3 +41,8 @@ object DerivedEq:
trait Coproduct[F[x] <: Eq[x], A](using inst: K0.CoproductInstances[F, A]) extends Eq[A]:
final override def eqv(x: A, y: A): Boolean = inst.fold2(x, y)(false):
[t] => (eqt: F[t], x: t, y: t) => eqt.eqv(x, y)

object Strict:
export DerivedEq.coproduct
given product[A](using K0.ProductInstances[Eq, A]): DerivedEq[A] =
new Product[Eq, A] {}
26 changes: 17 additions & 9 deletions core/src/main/scala-3/cats/derived/DerivedFoldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ object DerivedFoldable:
import DerivedFoldable.given
summonInline[DerivedFoldable[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: Foldable[F] =
import Strict.given
summonInline[DerivedFoldable[F]].instance

given [T]: DerivedFoldable[Const[T]] = new Foldable[Const[T]]:
def foldLeft[A, B](fa: T, b: B)(f: (B, A) => B): B = b
def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb

given nested[F[_], G[_]](using F: => Or[F], G: => Or[G]): DerivedFoldable[[x] =>> F[G[x]]] =
new Derived.Lazy(() => F.unify.compose(G.unify)) with Foldable[[x] =>> F[G[x]]]:
new Derived.Lazy(() => F.unify.compose(using G.unify)) with Foldable[[x] =>> F[G[x]]]:
export delegate.*

given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedFoldable[F] =
given K1.ProductInstances[Foldable, F] = inst.unify
new Product[Foldable, F] {}

given [F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedFoldable[F] =
given K1.CoproductInstances[Foldable, F] = inst.unify
new Coproduct[Foldable, F] {}
given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedFoldable[F] = Strict.product(using inst.unify)
given [F[_]](using => K1.CoproductInstances[Or, F]): DerivedFoldable[F] = Strict.coproduct

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedFoldable_F[F[_]: Or, G[_]: Or]: DerivedFoldable[[x] =>> F[G[x]]] = summon
protected given [F[_]: Or, G[_]: Or]: DerivedFoldable[[x] =>> F[G[x]]] = nested

trait Product[T[f[_]] <: Foldable[f], F[_]](using inst: K1.ProductInstances[T, F]) extends Foldable[F]:
final override def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B =
Expand All @@ -54,3 +54,11 @@ object DerivedFoldable:

final override def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
inst.fold(fa)([f[_]] => (F: T[f], fa: f[A]) => Eval.defer(F.foldRight(fa, lb)(f)))

object Strict:
given product[F[_]](using K1.ProductInstances[Foldable, F]): DerivedFoldable[F] =
new Product[Foldable, F] {}

given coproduct[F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedFoldable[F] =
given K1.CoproductInstances[Foldable, F] = inst.unify
new Coproduct[Foldable, F] {}
26 changes: 19 additions & 7 deletions core/src/main/scala-3/cats/derived/DerivedFunctor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,45 @@ object DerivedFunctor:
import DerivedFunctor.given
summonInline[DerivedFunctor[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: Functor[F] =
import Strict.given
summonInline[DerivedFunctor[F]].instance

given [T]: DerivedFunctor[Const[T]] = new Functor[Const[T]]:
def map[A, B](fa: T)(f: A => B): T = fa

given nested[F[_], G[_]](using F: => Or[F], G: => Or[G]): DerivedFunctor[[x] =>> F[G[x]]] =
new Derived.Lazy(() => F.unify.compose(G.unify)) with Functor[[x] =>> F[G[x]]]:
new Derived.Lazy(() => F.unify.compose(using G.unify)) with Functor[[x] =>> F[G[x]]]:
export delegate.*

given nested[F[_], G[_]](using
F: DerivedContravariant.Or[F],
G: DerivedContravariant.Or[G]
): DerivedFunctor[[x] =>> F[G[x]]] =
F.unify.compose(G.unify)
F.unify.compose(using G.unify)

given [F[_]](using inst: => K1.Instances[Or, F]): DerivedFunctor[F] =
given K1.Instances[Functor, F] = inst.unify
new Generic[Functor, F] {}
generic(using inst.unify)

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedFunctor_F[F[_], G[_]](using F: Or[F], G: Or[G]): DerivedFunctor[[x] =>> F[G[x]]] =
protected given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedFunctor[[x] =>> F[G[x]]] =
nested(using F, G)

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedFunctor_F[F[_], G[_]](using
protected given [F[_], G[_]](using
F: DerivedContravariant.Or[F],
G: DerivedContravariant.Or[G]
): DerivedFunctor[[x] =>> F[G[x]]] = nested(using F, G)
): DerivedFunctor[[x] =>> F[G[x]]] =
nested(using F, G)

private def generic[F[_]](using K1.Instances[Functor, F]): DerivedFunctor[F] =
new Generic[Functor, F] {}

trait Generic[T[f[_]] <: Functor[f], F[_]](using inst: K1.Instances[T, F]) extends Functor[F]:
final override def map[A, B](fa: F[A])(f: A => B): F[B] =
inst.map(fa)([f[_]] => (F: T[f], fa: f[A]) => F.map(fa)(f))

object Strict:
given product[F[_]](using K1.ProductInstances[Functor, F]): DerivedFunctor[F] = generic
given coproduct[F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedFunctor[F] = generic(using inst.unify)
Loading

0 comments on commit 076ffce

Please sign in to comment.