Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strict semiauto derivation for Scala 3 #626

Merged
merged 27 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e0a9a22
Strict semiauto derivation for Scala 3
joroKr21 Dec 17, 2023
dc14d0e
Strict derivation for Hash
joroKr21 Dec 17, 2023
64441d8
Strict derivation for Empty
joroKr21 Dec 17, 2023
e6e2f15
Strict derivation for Semigroup
joroKr21 Dec 17, 2023
5f0a0d1
Strict derivation for Monoid
joroKr21 Dec 18, 2023
42a78b8
Strict derivation for Order
joroKr21 Dec 18, 2023
ea8ecd3
Strict derivation for CommutativeSemigroup
joroKr21 Dec 18, 2023
c7edde6
Strict derivation for CommutativeMonoid
joroKr21 Dec 23, 2023
76ae641
Deprecate nameless givens
joroKr21 Dec 23, 2023
f26eb06
Strict derivation for Show
joroKr21 Dec 23, 2023
cc70a4a
Strict derivation for PartialOrder
joroKr21 Dec 23, 2023
f88f900
Strict derivation for ShowPretty
joroKr21 Dec 23, 2023
ac996e2
Strict derivation for EmptyK
joroKr21 Dec 24, 2023
bf05219
Strict derivation for SemigroupK
joroKr21 Jan 6, 2024
125907c
Strict derivation for MonoidK
joroKr21 Jan 6, 2024
8988ea4
Strict derivation for Pure
joroKr21 Jan 7, 2024
63adddc
Strict derivation for Invariant
joroKr21 Jan 7, 2024
9b57ab6
Reduce number of deprecated methods
joroKr21 Jan 7, 2024
a0ff7e4
Strict derivation for Functor
joroKr21 Jan 7, 2024
9cfde46
Strict derivation for Contravariant
joroKr21 Jan 7, 2024
4e35345
Strict derivation for Foldable
joroKr21 Jan 7, 2024
f7341a3
Strict validation for Apply
joroKr21 Jan 7, 2024
6a417a0
Strict derivation for Applicative
joroKr21 Jan 7, 2024
e3277d9
Strict derivation for Reducible
joroKr21 Jan 7, 2024
e0641b4
Strict derivation for Traverse
joroKr21 Jan 7, 2024
93b2e92
Strict derivation for NonEmptyTraverse
joroKr21 Jan 7, 2024
7845b98
Restrict strict derivation to product and coproduct
joroKr21 Jan 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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