Skip to content

Commit

Permalink
Make nested instances lazy (#627)
Browse files Browse the repository at this point in the history
* Make nested instances lazy

* Add tests for lazy nested instances
  • Loading branch information
joroKr21 authored Jan 3, 2024
1 parent 51c8e91 commit bc35f8f
Show file tree
Hide file tree
Showing 22 changed files with 238 additions and 148 deletions.
6 changes: 5 additions & 1 deletion core/src/main/scala-3/cats/derived/Derived.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package cats.derived

import shapeless3.deriving.*

import scala.annotation.*
import scala.compiletime.*
import scala.compiletime.summonFrom

@implicitNotFound("Could not derive an instance of ${A}")
opaque type Derived[A] = A
Expand All @@ -29,6 +30,9 @@ 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:
final protected lazy val delegate: A = f()

sealed abstract class OrInstances:
inline given [A]: Derived.Or[A] = summonFrom {
case instance: A => Derived.Or(instance)
Expand Down
23 changes: 12 additions & 11 deletions core/src/main/scala-3/cats/derived/DerivedApplicative.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package cats.derived

import shapeless3.deriving.{Const, K1}
import cats.{Applicative, Monoid}

import scala.compiletime.*
import shapeless3.deriving.{Continue, K0, Labelling}
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
import scala.deriving.Mirror
import scala.compiletime.*

@implicitNotFound("""Could not derive an instance of Applicative[F] where F = ${F}.
Make sure that F[_] satisfies one of the following conditions:
Expand All @@ -23,17 +20,21 @@ object DerivedApplicative:
summonInline[DerivedApplicative[F]].instance

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

given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedApplicative[[x] =>> F[G[x]]] =
F.unify.compose(G.unify)
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]]]:
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] {}

trait Product[T[x[_]] <: Applicative[x], F[_]](using inst: K1.ProductInstances[T, F])
@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedApplicative_F[F[_]: Or, G[_]: Or]: DerivedApplicative[[x] =>> F[G[x]]] = summon

trait Product[T[f[_]] <: Applicative[f], F[_]](using inst: K1.ProductInstances[T, F])
extends Applicative[F],
DerivedApply.Product[T, F]:
override def pure[A](x: A): F[A] = inst.construct([t[_]] => (apl: T[t]) => apl.pure[A](x))
final override def pure[A](x: A): F[A] = inst.construct([f[_]] => (F: T[f]) => F.pure[A](x))
29 changes: 15 additions & 14 deletions core/src/main/scala-3/cats/derived/DerivedApply.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package cats.derived

import shapeless3.deriving.{Const, K1}
import cats.{Apply, Semigroup}

import scala.compiletime.*
import shapeless3.deriving.{Continue, K0, Labelling}
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
import scala.deriving.Mirror
import scala.compiletime.*

@implicitNotFound("""Could not derive an instance of Apply[F] where F = ${F}.
Make sure that F[_] satisfies one of the following conditions:
Expand All @@ -23,18 +20,22 @@ object DerivedApply:
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.combine(ff, fa)
def map[A, B](fa: T)(f: A => B) = fa
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 [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedApply[[x] =>> F[G[x]]] =
F.unify.compose(G.unify)
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]]]:
export delegate.*

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

trait Product[T[x[_]] <: Apply[x], F[_]](using inst: K1.ProductInstances[T, F]) extends Apply[F]:
override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] =
inst.map2(ff, fa)([t[_]] => (apl: T[t], tt: t[A => B], ta: t[A]) => apl.ap(tt)(ta))
override def map[A, B](fa: F[A])(f: A => B): F[B] =
inst.map(fa: F[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.map(fa)(f))
@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedApply_F[F[_]: Or, G[_]: Or]: DerivedApply[[x] =>> F[G[x]]] = summon

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))
16 changes: 10 additions & 6 deletions core/src/main/scala-3/cats/derived/DerivedContravariant.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.derived

import cats.{Contravariant, Functor}
import cats.Contravariant
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
Expand All @@ -22,14 +22,18 @@ object DerivedContravariant:
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 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]]]:
export delegate.*

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]:
@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

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[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.contramap(fa)(f))
inst.map(fa)([f[_]] => (T: T[f], fa: f[A]) => T.contramap(fa)(f))
39 changes: 25 additions & 14 deletions core/src/main/scala-3/cats/derived/DerivedEmptyK.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.derived

import alleycats.{Empty, EmptyK, Pure}
import alleycats.{Empty, EmptyK}
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
Expand All @@ -21,21 +21,32 @@ object DerivedEmptyK:
import DerivedEmptyK.given
summonInline[DerivedEmptyK[F]].instance

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

given [F[_], G[_]](using F: Or[F]): DerivedEmptyK[[x] =>> F[G[x]]] =
new EmptyK[[x] =>> F[G[x]]]:
def empty[A] = F.unify.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 [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]]]:
def empty[A] = F.unify.pure(G.unify.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]:
def empty[A]: F[A] = inst.unify.construct([f[_]] => (E: EmptyK[f]) => E.empty[A])
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]] => (ek: Or[f]) => ek.unify.asInstanceOf[EmptyK[F]])
gen.withOnly[Or, EmptyK[F]]([f[x] <: F[x]] => (F: Or[f]) => F.unify.asInstanceOf[EmptyK[F]])

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

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedEmptyK_F[F[_]: DerivedPure.Or, G[_]: Or](
ev: NotGiven[Or[F]]
): DerivedEmptyK[[x] =>> F[G[x]]] = nested(using ev)
23 changes: 13 additions & 10 deletions core/src/main/scala-3/cats/derived/DerivedFoldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ object DerivedFoldable:
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 [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedFoldable[[x] =>> F[G[x]]] =
F.unify.compose(G.unify)
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]]]:
export delegate.*

given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedFoldable[F] =
given K1.ProductInstances[Foldable, F] = inst.unify
Expand All @@ -34,18 +35,20 @@ object DerivedFoldable:
given K1.CoproductInstances[Foldable, F] = inst.unify
new Coproduct[Foldable, F] {}

trait Product[T[x[_]] <: Foldable[x], F[_]](using inst: K1.ProductInstances[T, F]) extends Foldable[F]:
@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedFoldable_F[F[_]: Or, G[_]: Or]: DerivedFoldable[[x] =>> F[G[x]]] = summon

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 =
inst.foldLeft[A, B](fa)(b)([f[_]] => (acc: B, tf: T[f], fa: f[A]) => Continue(tf.foldLeft(fa, acc)(f)))
inst.foldLeft(fa)(b)([f[_]] => (b: B, F: T[f], fa: f[A]) => Continue(F.foldLeft(fa, b)(f)))

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

trait Coproduct[T[x[_]] <: Foldable[x], F[_]](using inst: K1.CoproductInstances[T, F]) extends Foldable[F]:
trait Coproduct[T[f[_]] <: Foldable[f], F[_]](using inst: K1.CoproductInstances[T, F]) extends Foldable[F]:
final override def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B =
inst.fold[A, B](fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.foldLeft(fa, b)(f))
inst.fold(fa)([f[_]] => (F: T[f], fa: f[A]) => F.foldLeft(fa, b)(f))

final override def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
inst.fold[A, Eval[B]](fa)([f[_]] => (tf: T[f], fa: f[A]) => Eval.defer(tf.foldRight(fa, lb)(f)))
inst.fold(fa)([f[_]] => (F: T[f], fa: f[A]) => Eval.defer(F.foldRight(fa, lb)(f)))
26 changes: 18 additions & 8 deletions core/src/main/scala-3/cats/derived/DerivedFunctor.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.derived

import cats.{Contravariant, Functor}
import cats.Functor
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
Expand All @@ -23,20 +23,30 @@ object DerivedFunctor:
given [T]: DerivedFunctor[Const[T]] = new Functor[Const[T]]:
def map[A, B](fa: T)(f: A => B): T = fa

given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedFunctor[[x] =>> F[G[x]]] =
F.unify.compose(G.unify)
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]]]:
export delegate.*

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

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

trait Generic[T[x[_]] <: Functor[x], F[_]](using inst: K1.Instances[T, F]) extends Functor[F]:
@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]]] =
nested(using F, G)

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

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[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.map(fa)(f))
inst.map(fa)([f[_]] => (F: T[f], fa: f[A]) => F.map(fa)(f))
19 changes: 10 additions & 9 deletions core/src/main/scala-3/cats/derived/DerivedInvariant.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package cats.derived

import cats.{Contravariant, Functor, Invariant}
import cats.Invariant
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
import scala.compiletime.*
import scala.util.NotGiven

@implicitNotFound("""Could not derive an instance of Invariant[F] where F = ${F}.
Make sure that F[_] satisfies one of the following conditions:
Expand All @@ -23,15 +22,17 @@ object DerivedInvariant:
given [T]: DerivedInvariant[Const[T]] = new Invariant[Const[T]]:
def imap[A, B](fa: T)(f: A => B)(g: B => A): T = fa

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

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

trait Generic[T[x[_]] <: Invariant[x], F[_]](using inst: K1.Instances[T, F]) extends Invariant[F]:
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = inst.map(fa)(
[t[_]] => (inv: T[t], t0: t[A]) => inv.imap(t0)(f)(g)
)
@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedInvariant_F[F[_]: Or, G[_]: Or]: DerivedInvariant[[x] =>> F[G[x]]] = summon

trait Generic[T[f[_]] <: Invariant[f], F[_]](using inst: K1.Instances[T, F]) extends Invariant[F]:
final override def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] =
inst.map(fa)([f[_]] => (F: T[f], fa: f[A]) => F.imap(fa)(f)(g))
46 changes: 25 additions & 21 deletions core/src/main/scala-3/cats/derived/DerivedMonoidK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,35 @@ object DerivedMonoidK:
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(_, _))
def empty[A]: T = T.empty
def combineK[A](x: T, y: T): T = T.combine(x, y)

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

given nested[F[_], G[_]](using NotGiven[Or[F]])(using
F: DerivedApplicative.Or[F],
G: => Or[G]
): DerivedMonoidK[[x] =>> F[G[x]]] = new MonoidK[[x] =>> F[G[x]]]:
val f: Applicative[F] = F.unify
lazy val g: MonoidK[G] = G.unify
def empty[A]: F[G[A]] = f.pure(g.empty[A])
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])
@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedMonoidK_F[F[_]: Or, G[_]]: DerivedMonoidK[[x] =>> F[G[x]]] = summon

@deprecated("Kept for binary compatibility", "3.2.0")
private[derived] def given_DerivedMonoidK_F[F[_]: DerivedApplicative.Or, G[_]: Or](
ev: NotGiven[Or[F]]
): DerivedMonoidK[[x] =>> F[G[x]]] = nested(using ev)

trait Product[T[f[_]] <: MonoidK[f], 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])
final override def empty[A]: F[A] = inst.construct([f[_]] => (F: T[f]) => F.empty[A])
Loading

0 comments on commit bc35f8f

Please sign in to comment.