Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 51 additions & 3 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, Eval, Functor}
import shapeless3.deriving.{Const, Derived}
import shapeless3.deriving.K1.*

Expand All @@ -24,6 +24,13 @@ object DerivedContravariant:
import Strict.given
summonInline[DerivedContravariant[F]].instance

/** Stack-safe (trampolined via [[cats.Eval]]) derivation. Opt-in: slower on shallow data, but does not overflow the
* stack on deeply nested recursive ADTs.
*/
inline def stackSafe[F[_]]: Contravariant[F] =
import StackSafe.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

Expand All @@ -34,13 +41,18 @@ object DerivedContravariant:
new Lazy(() => F.unify.composeContravariant(using G.unify)) with Contravariant[F <<< G]:
export delegate.*

given [F[_]](using inst: => Instances[Contravariant |: Derived, F]): DerivedContravariant[F] =
generic(using inst.unify)
given [F[_]](using inst: ProductInstances[Contravariant |: Derived, F]): DerivedContravariant[F] =
Strict.product(using inst.unify)

given [F[_]](using => CoproductInstances[Contravariant |: Derived, F]): DerivedContravariant[F] =
Strict.coproduct

@deprecated("Kept for binary compatibility", "3.2.0")
protected given [F[_]: Functor |: Derived, G[_]: Contravariant |: Derived]: DerivedContravariant[[x] =>> F[G[x]]] =
nested

// ---- Default: fast direct recursion ----

private def generic[F[_]: InstancesOf[Contravariant]]: DerivedContravariant[F] =
new Generic[Contravariant, F] {}

Expand All @@ -52,3 +64,39 @@ object DerivedContravariant:
given product[F[_]: ProductInstancesOf[Contravariant]]: DerivedContravariant[F] = generic
given coproduct[F[_]](using inst: => CoproductInstances[Contravariant |: Derived, F]): DerivedContravariant[F] =
generic(using inst.unify)

// ---- Opt-in: stack-safe recursion via Eval ----

object StackSafe:
given product[F[_]](using inst: ProductInstances[Contravariant |: Derived, F]): DerivedContravariant[F] =
given ProductInstances[Contravariant, F] = inst.unify
new SafeProduct[Contravariant, F] {}

given coproduct[F[_]](using inst: => CoproductInstances[Contravariant |: Derived, F]): DerivedContravariant[F] =
given CoproductInstances[Contravariant, F] = inst.unify
new SafeCoproduct[Contravariant, F] {}

private[derived] trait Safe[F[_]] extends Contravariant[F]:
private[derived] def safeContramap[A, B](fa: F[A])(f: B => A): Eval[F[B]]
override def contramap[A, B](fa: F[A])(f: B => A): F[B] = safeContramap(fa)(f).value

private[derived] def safeContramap[F[_], A, B](F: Contravariant[F])(fa: F[A])(f: B => A): Eval[F[B]] =
F match
case safe: Safe[F] @scala.unchecked => safe.safeContramap(fa)(f)
case _ => Eval.later(F.contramap(fa)(f))

trait SafeProduct[T[f[_]] <: Contravariant[f], F[_]](using inst: ProductInstances[T, F]) extends Safe[F]:
private[derived] final override def safeContramap[A, B](fa: F[A])(f: B => A): Eval[F[B]] =
val pure = [a] => (x: a) => Eval.now(x)
val mp = [a, b] => (ea: Eval[a], g: a => b) => ea.map(g)
val ap = [a, b] => (ef: Eval[a => b], ea: Eval[a]) => ef.flatMap(g => ea.map(g))
inst.traverse[A, Eval, B](fa)(mp)(pure)(ap)(
[f[_]] => (F: T[f], fa: f[A]) => DerivedContravariant.safeContramap(F)(fa)(f)
)

trait SafeCoproduct[T[f[_]] <: Contravariant[f], F[_]](using inst: CoproductInstances[T, F]) extends Safe[F]:
private[derived] final override def safeContramap[A, B](fa: F[A])(f: B => A): Eval[F[B]] =
Eval.defer(inst.fold(fa)(
[f[a] <: F[a]] => (F: T[f], fa: f[A]) =>
DerivedContravariant.safeContramap(F)(fa)(f).asInstanceOf[Eval[F[B]]]
))
45 changes: 44 additions & 1 deletion core/src/main/scala-3/cats/derived/DerivedEq.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.derived

import cats.Eq
import cats.{Eq, Eval}
import shapeless3.deriving.{Complete, Derived}
import shapeless3.deriving.K0.*

Expand All @@ -22,6 +22,13 @@ object DerivedEq:
import Strict.given
summonInline[DerivedEq[A]].instance

/** Stack-safe (trampolined via [[cats.Eval]]) derivation. Opt-in: slower on shallow data, but does not overflow the
* stack on deeply nested recursive ADTs.
*/
inline def stackSafe[A]: Eq[A] =
import StackSafe.given
summonInline[DerivedEq[A]].instance

@unused
given singleton[A <: Singleton: ValueOf]: DerivedEq[A] =
Eq.allEqual
Expand All @@ -33,6 +40,8 @@ object DerivedEq:
given CoproductInstances[Eq, A] = inst.unify
new Coproduct[Eq, A] {}

// ---- Default: fast direct recursion ----

trait Product[F[x] <: Eq[x], A](using inst: ProductInstances[F, A]) extends Eq[A]:
final override def eqv(x: A, y: A): Boolean = inst.foldLeft2(x, y)(true: Boolean):
[t] => (acc: Boolean, eqt: F[t], x: t, y: t) => Complete(!eqt.eqv(x, y))(false)(acc)
Expand All @@ -45,3 +54,37 @@ object DerivedEq:
export DerivedEq.coproduct
given product[A: ProductInstancesOf[Eq]]: DerivedEq[A] =
new Product[Eq, A] {}

// ---- Opt-in: stack-safe recursion via Eval ----

object StackSafe:
given product[A](using inst: => ProductInstances[Eq |: Derived, A]): DerivedEq[A] =
given ProductInstances[Eq, A] = inst.unify
new SafeProduct[Eq, A] {}

given coproduct[A](using inst: => CoproductInstances[Eq |: Derived, A]): DerivedEq[A] =
given CoproductInstances[Eq, A] = inst.unify
new SafeCoproduct[Eq, A] {}

private[derived] trait Safe[A] extends Eq[A]:
private[derived] def safeEqv(x: A, y: A): Eval[Boolean]
override def eqv(x: A, y: A): Boolean = safeEqv(x, y).value

private[derived] def safeEqv[A](F: Eq[A])(x: A, y: A): Eval[Boolean] =
F.asInstanceOf[Matchable] match
case safe: Safe[?] => safe.asInstanceOf[Safe[A]].safeEqv(x, y)
case _ => Eval.later(F.eqv(x, y))

trait SafeProduct[F[x] <: Eq[x], A](using inst: ProductInstances[F, A]) extends Safe[A]:
private[derived] final override def safeEqv(x: A, y: A): Eval[Boolean] =
inst.foldLeft2[Eval[Boolean]](x, y)(Eval.now(true)):
[t] => (acc: Eval[Boolean], eqt: F[t], xt: t, yt: t) =>
val next = acc.flatMap: b =>
if !b then Eval.now(false) else DerivedEq.safeEqv(eqt)(xt, yt)
Complete(false)(next)(next)

trait SafeCoproduct[F[x] <: Eq[x], A](using inst: CoproductInstances[F, A]) extends Safe[A]:
private[derived] final override def safeEqv(x: A, y: A): Eval[Boolean] =
Eval.defer(inst.fold2(x, y)(Eval.now(false): Eval[Boolean]):
[t] => (eqt: F[t], xt: t, yt: t) => DerivedEq.safeEqv(eqt)(xt, yt)
)
48 changes: 48 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedFoldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ object DerivedFoldable:
import Strict.given
summonInline[DerivedFoldable[F]].instance

/** Stack-safe (trampolined via [[cats.Eval]]) derivation. Opt-in: slower on shallow data, but does not overflow the
* stack on deeply nested recursive ADTs.
*/
inline def stackSafe[F[_]]: Foldable[F] =
import StackSafe.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
Expand All @@ -44,6 +51,8 @@ object DerivedFoldable:
@deprecated("Kept for binary compatibility", "3.2.0")
protected given [F[_]: Foldable |: Derived, G[_]: Foldable |: Derived]: DerivedFoldable[[x] =>> F[G[x]]] = nested

// ---- Default: fast direct recursion ----

trait Product[T[f[_]] <: Foldable[f], F[_]](using inst: ProductInstances[T, F]) extends Foldable[F]:
final override def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B =
inst.foldLeft(fa)(b)([f[_]] => (b: B, F: T[f], fa: f[A]) => F.foldLeft(fa, b)(f))
Expand All @@ -65,3 +74,42 @@ object DerivedFoldable:
given coproduct[F[_]](using inst: => CoproductInstances[Foldable |: Derived, F]): DerivedFoldable[F] =
given CoproductInstances[Foldable, F] = inst.unify
new Coproduct[Foldable, F] {}

// ---- Opt-in: stack-safe recursion via Eval ----

object StackSafe:
given product[F[_]](using inst: ProductInstances[Foldable |: Derived, F]): DerivedFoldable[F] =
given ProductInstances[Foldable, F] = inst.unify
new SafeProduct[Foldable, F] {}

given coproduct[F[_]](using inst: => CoproductInstances[Foldable |: Derived, F]): DerivedFoldable[F] =
given CoproductInstances[Foldable, F] = inst.unify
new SafeCoproduct[Foldable, F] {}

private[derived] trait Safe[F[_]] extends Foldable[F]:
private[derived] def safeFoldLeft[A, B](fa: F[A], b: Eval[B])(f: (B, A) => B): Eval[B]
override def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B =
safeFoldLeft(fa, Eval.now(b))(f).value

private[derived] def safeFoldLeft[F[_], A, B](F: Foldable[F])(fa: F[A], b: Eval[B])(f: (B, A) => B): Eval[B] =
F match
case safe: Safe[F] @scala.unchecked => safe.safeFoldLeft(fa, b)(f)
case _ => b.map(F.foldLeft(fa, _)(f))

trait SafeProduct[T[f[_]] <: Foldable[f], F[_]](using inst: ProductInstances[T, F]) extends Safe[F]:
private[derived] final override def safeFoldLeft[A, B](fa: F[A], b: Eval[B])(f: (B, A) => B): Eval[B] =
inst.foldLeft[A, Eval[B]](fa)(b):
[f[_]] => (acc: Eval[B], F: T[f], fa: f[A]) =>
DerivedFoldable.safeFoldLeft(F)(fa, acc)(f)

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

trait SafeCoproduct[T[f[_]] <: Foldable[f], F[_]](using inst: CoproductInstances[T, F]) extends Safe[F]:
private[derived] final override def safeFoldLeft[A, B](fa: F[A], b: Eval[B])(f: (B, A) => B): Eval[B] =
Eval.defer(inst.fold(fa)([f[_]] => (F: T[f], fa: f[A]) =>
DerivedFoldable.safeFoldLeft(F)(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(fa)([f[_]] => (F: T[f], fa: f[A]) => Eval.defer(F.foldRight(fa, lb)(f)))
54 changes: 51 additions & 3 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.{Contravariant, Eval, Functor}
import shapeless3.deriving.{Const, Derived}
import shapeless3.deriving.K1.*

Expand All @@ -25,6 +25,13 @@ object DerivedFunctor:
import Strict.given
summonInline[DerivedFunctor[F]].instance

/** Stack-safe (trampolined via [[cats.Eval]]) derivation. Opt-in: slower on shallow data, but does not overflow the
* stack on deeply nested recursive ADTs.
*/
inline def stackSafe[F[_]]: Functor[F] =
import StackSafe.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

Expand All @@ -41,8 +48,11 @@ object DerivedFunctor:
): DerivedFunctor[F <<< G] =
F.unify.compose(using G.unify)

given [F[_]](using inst: => Instances[Functor |: Derived, F]): DerivedFunctor[F] =
generic(using inst.unify)
given [F[_]](using inst: ProductInstances[Functor |: Derived, F]): DerivedFunctor[F] =
Strict.product(using inst.unify)

given [F[_]](using => CoproductInstances[Functor |: Derived, F]): DerivedFunctor[F] =
Strict.coproduct

@deprecated("Kept for binary compatibility", "3.2.0")
protected given [F[_], G[_]](using
Expand All @@ -58,6 +68,8 @@ object DerivedFunctor:
): DerivedFunctor[[x] =>> F[G[x]]] =
nested(using F, G)

// ---- Default: fast direct recursion ----

private def generic[F[_]: InstancesOf[Functor]]: DerivedFunctor[F] =
new Generic[Functor, F] {}

Expand All @@ -69,3 +81,39 @@ object DerivedFunctor:
given product[F[_]: ProductInstancesOf[Functor]]: DerivedFunctor[F] = generic
given coproduct[F[_]](using inst: => CoproductInstances[Functor |: Derived, F]): DerivedFunctor[F] =
generic(using inst.unify)

// ---- Opt-in: stack-safe recursion via Eval ----

object StackSafe:
given product[F[_]](using inst: ProductInstances[Functor |: Derived, F]): DerivedFunctor[F] =
given ProductInstances[Functor, F] = inst.unify
new SafeProduct[Functor, F] {}

given coproduct[F[_]](using inst: => CoproductInstances[Functor |: Derived, F]): DerivedFunctor[F] =
given CoproductInstances[Functor, F] = inst.unify
new SafeCoproduct[Functor, F] {}

private[derived] trait Safe[F[_]] extends Functor[F]:
private[derived] def safeMap[A, B](fa: F[A])(f: A => B): Eval[F[B]]
override def map[A, B](fa: F[A])(f: A => B): F[B] = safeMap(fa)(f).value

private[derived] def safeMap[F[_], A, B](F: Functor[F])(fa: F[A])(f: A => B): Eval[F[B]] =
F match
case safe: Safe[F] @scala.unchecked => safe.safeMap(fa)(f)
case _ => Eval.later(F.map(fa)(f))

trait SafeProduct[T[f[_]] <: Functor[f], F[_]](using inst: ProductInstances[T, F]) extends Safe[F]:
private[derived] final override def safeMap[A, B](fa: F[A])(f: A => B): Eval[F[B]] =
val pure = [a] => (x: a) => Eval.now(x)
val mp = [a, b] => (ea: Eval[a], g: a => b) => ea.map(g)
val ap = [a, b] => (ef: Eval[a => b], ea: Eval[a]) => ef.flatMap(g => ea.map(g))
inst.traverse[A, Eval, B](fa)(mp)(pure)(ap)(
[f[_]] => (F: T[f], fa: f[A]) => DerivedFunctor.safeMap(F)(fa)(f)
)

trait SafeCoproduct[T[f[_]] <: Functor[f], F[_]](using inst: CoproductInstances[T, F]) extends Safe[F]:
private[derived] final override def safeMap[A, B](fa: F[A])(f: A => B): Eval[F[B]] =
Eval.defer(inst.fold(fa)(
[f[a] <: F[a]] => (F: T[f], fa: f[A]) =>
DerivedFunctor.safeMap(F)(fa)(f).asInstanceOf[Eval[F[B]]]
))
52 changes: 51 additions & 1 deletion core/src/main/scala-3/cats/derived/DerivedHash.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.derived

import cats.Hash
import cats.{Eval, Hash}
import shapeless3.deriving.Derived
import shapeless3.deriving.K0.*

Expand All @@ -23,6 +23,13 @@ object DerivedHash:
import Strict.given
summonInline[DerivedHash[A]].instance

/** Stack-safe (trampolined via [[cats.Eval]]) derivation. Opt-in: slower on shallow data, but does not overflow the
* stack on deeply nested recursive ADTs.
*/
inline def stackSafe[A]: Hash[A] =
import StackSafe.given
summonInline[DerivedHash[A]].instance

// These instances support singleton types unlike the instances in Cats' kernel.
given boolean[A <: Boolean]: DerivedHash[A] = Hash.fromUniversalHashCode
given byte[A <: Byte]: DerivedHash[A] = Hash.fromUniversalHashCode
Expand All @@ -42,6 +49,8 @@ object DerivedHash:
given CoproductInstances[Hash, A] = inst.unify
new Coproduct[Hash, A] {}

// ---- Default: fast direct recursion ----

trait Product[F[x] <: Hash[x], A <: scala.Product](using inst: ProductInstances[F, A])
extends DerivedEq.Product[F, A],
Hash[A]:
Expand All @@ -62,3 +71,44 @@ object DerivedHash:
export DerivedHash.coproduct
given product[A <: scala.Product: ProductInstancesOf[Hash]]: DerivedHash[A] =
new Product[Hash, A] {}

// ---- Opt-in: stack-safe recursion via Eval ----

object StackSafe:
given product[A <: scala.Product](using inst: => ProductInstances[Hash |: Derived, A]): DerivedHash[A] =
given ProductInstances[Hash, A] = inst.unify
new SafeProduct[Hash, A] {}

given coproduct[A](using inst: => CoproductInstances[Hash |: Derived, A]): DerivedHash[A] =
given CoproductInstances[Hash, A] = inst.unify
new SafeCoproduct[Hash, A] {}

private[derived] trait Safe[A] extends Hash[A]:
private[derived] def safeHash(x: A): Eval[Int]
override def hash(x: A): Int = safeHash(x).value

private[derived] def safeHash[A](F: Hash[A])(x: A): Eval[Int] =
F.asInstanceOf[Matchable] match
case safe: Safe[?] => safe.asInstanceOf[Safe[A]].safeHash(x)
case _ => Eval.later(F.hash(x))

trait SafeProduct[F[x] <: Hash[x], A <: scala.Product](using inst: ProductInstances[F, A])
extends DerivedEq.SafeProduct[F, A],
Safe[A]:

private[derived] final override def safeHash(x: A): Eval[Int] =
val arity = x.productArity
val prefix = x.productPrefix.hashCode
if arity <= 0 then Eval.now(prefix)
else
val seed = MurmurHash3.mix(MurmurHash3.productSeed, prefix)
inst.foldLeft[Eval[Int]](x)(Eval.now(seed)):
[t] => (acc: Eval[Int], h: F[t], xt: t) =>
acc.flatMap(a => DerivedHash.safeHash(h)(xt).map(hh => MurmurHash3.mix(a, hh)))
.map(MurmurHash3.finalizeHash(_, arity))

trait SafeCoproduct[F[x] <: Hash[x], A](using inst: CoproductInstances[F, A])
extends DerivedEq.SafeCoproduct[F, A],
Safe[A]:
private[derived] final override def safeHash(x: A): Eval[Int] =
Eval.defer(inst.fold[Eval[Int]](x)([t] => (h: F[t], xt: t) => DerivedHash.safeHash(h)(xt)))
Loading
Loading