diff --git a/core/src/main/scala-3/cats/derived/all.scala b/core/src/main/scala-3/cats/derived/all.scala index 05566fd0..7a707251 100644 --- a/core/src/main/scala-3/cats/derived/all.scala +++ b/core/src/main/scala-3/cats/derived/all.scala @@ -15,6 +15,7 @@ object all extends MonoidKDerivation, OrderDerivation, PartialOrderDerivation, + ReducibleDerivation, SemigroupDerivation, SemigroupKDerivation, ShowDerivation, diff --git a/core/src/main/scala-3/cats/derived/foldable.scala b/core/src/main/scala-3/cats/derived/foldable.scala index d97f6dfa..2f4be05f 100644 --- a/core/src/main/scala-3/cats/derived/foldable.scala +++ b/core/src/main/scala-3/cats/derived/foldable.scala @@ -2,42 +2,62 @@ package cats.derived import cats.{Eval, Foldable} import shapeless3.deriving.{Const, Continue, K1} +import scala.annotation.threadUnsafe object foldable extends FoldableDerivation, Instances -trait ProductFoldable[T[x[_]] <: Foldable[x], F[_]](using inst: K1.ProductInstances[T, F]) - extends Foldable[F]: +trait DerivedFoldable[F[_]] extends Foldable[F] +object DerivedFoldable extends DerivedFoldableLowPriority: + given const[T]: DerivedFoldable[Const[T]] with + 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 - def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = - inst.foldLeft[A, B](fa)(b)( - [t[_]] => (acc: B, fd: T[t], t0: t[A]) => Continue(fd.foldLeft(t0, acc)(f)) - ) + given delegated[F[_]](using F: => Foldable[F]): DerivedFoldable[F] = + new Delegated(F) - 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)( - [t[_]] => (fd: T[t], t0: t[A], acc: Eval[B]) => Continue(fd.foldRight(t0, acc)(f)) - ) + given composed[F[_]: DerivedFoldable, G[_]: DerivedFoldable]: DerivedFoldable[[x] =>> F[G[x]]] = + new Delegated(Foldable[F].compose[G]) -trait CoproductFoldable[T[x[_]] <: Foldable[x], F[_]](using inst: K1.CoproductInstances[T, F]) - extends Foldable[F]: + def product[F[_]](using K1.ProductInstances[DerivedFoldable, F]): DerivedFoldable[F] = + new Product[DerivedFoldable, F] {} + + def coproduct[F[_]](using K1.CoproductInstances[DerivedFoldable, F]): DerivedFoldable[F] = + new Coproduct[DerivedFoldable, F] {} - def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = - inst.fold[A, B](fa)( - [t[_]] => (fd: T[t], t0: t[A]) => fd.foldLeft(t0, b)(f) - ) + trait Product[T[x[_]] <: Foldable[x], F[_]](using inst: K1.ProductInstances[T, F]) + extends DerivedFoldable[F]: - def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - inst.fold[A, Eval[B]](fa)( - [t[_]] => (fd: T[t], t0: t[A]) => fd.foldRight(t0, lb)(f) - ) + 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)) + } -trait FoldableDerivation: - extension (F: Foldable.type) - inline def derived[F[_]](using gen: K1.Generic[F]): Foldable[F] = - gen.derive(productFoldable[F], coproductFoldable[F]) + 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))) + } + + trait Coproduct[T[x[_]] <: Foldable[x], F[_]](using inst: K1.CoproductInstances[T, F]) + extends DerivedFoldable[F]: + + 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) + } - given productFoldable[F[_]](using inst: => K1.ProductInstances[Foldable, F]): Foldable[F] = - new ProductFoldable[Foldable, F]{} + 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)) + } - given coproductFoldable[F[_]](using inst: => K1.CoproductInstances[Foldable, F]): Foldable[F] = - new CoproductFoldable[Foldable, F]{} + private final class Delegated[F[_]](F: => Foldable[F]) extends DerivedFoldable[F]: + @threadUnsafe private lazy val underlying = F + export underlying._ + +private[derived] sealed abstract class DerivedFoldableLowPriority: + inline given derived[F[_]](using gen: K1.Generic[F]): DerivedFoldable[F] = + gen.derive(DerivedFoldable.product, DerivedFoldable.coproduct) + +trait FoldableDerivation: + extension (F: Foldable.type) + def derived[F[_]](using instance: DerivedFoldable[F]): Foldable[F] = instance diff --git a/core/src/main/scala-3/cats/derived/functor.scala b/core/src/main/scala-3/cats/derived/functor.scala index d6e1ff64..b87f0051 100644 --- a/core/src/main/scala-3/cats/derived/functor.scala +++ b/core/src/main/scala-3/cats/derived/functor.scala @@ -2,18 +2,40 @@ package cats.derived import cats.Functor import shapeless3.deriving.{Const, K1} +import scala.annotation.threadUnsafe object functor extends FunctorDerivation, Instances -trait GenericFunctor[T[x[_]] <: Functor[x], F[_]](using inst: K1.Instances[T, F]) - extends Functor[F]: +trait DerivedFunctor[F[_]] extends Functor[F] +object DerivedFunctor extends DerivedFunctorLowPriority: + given const[T]: DerivedFunctor[Const[T]] with + def map[A, B](fa: T)(f: A => B): T = fa - def map[A, B](fa: F[A])(f: A => B): F[B] = - inst.map(fa : F[A])( - [t[_]] => (func: T[t], t0: t[A]) => func.map(t0)(f) - ) + given delegated[F[_]](using F: => Functor[F]): DerivedFunctor[F] = + new Delegated(F) + + given composed[F[_]: DerivedFunctor, G[_]: DerivedFunctor]: DerivedFunctor[[x] =>> F[G[x]]] = + new Delegated(Functor[F].compose[G]) + + def generic[F[_]](using K1.Instances[DerivedFunctor, F]): DerivedFunctor[F] = + new Generic[DerivedFunctor, F] {} + + trait Generic[T[x[_]] <: Functor[x], F[_]](using inst: K1.Instances[T, F]) + extends DerivedFunctor[F]: + + 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) + } + + private final class Delegated[F[_]](F: => Functor[F]) extends DerivedFunctor[F]: + @threadUnsafe private lazy val underlying = F + export underlying._ + +private[derived] sealed abstract class DerivedFunctorLowPriority: + inline given derived[F[_]](using K1.Generic[F]): DerivedFunctor[F] = + DerivedFunctor.generic trait FunctorDerivation: extension (F: Functor.type) - inline def derived[F[_]](using gen: K1.Generic[F]): Functor[F] = - new GenericFunctor[Functor, F]{} + def derived[F[_]](using instance: DerivedFunctor[F]): Functor[F] = instance diff --git a/core/src/main/scala-3/cats/derived/reducible.scala b/core/src/main/scala-3/cats/derived/reducible.scala new file mode 100644 index 00000000..83f47c57 --- /dev/null +++ b/core/src/main/scala-3/cats/derived/reducible.scala @@ -0,0 +1,72 @@ +package cats.derived + +import cats.{Eval, Foldable, Reducible} +import shapeless3.deriving.{Continue, Const, K1} +import scala.annotation.threadUnsafe + +object reducible extends ReducibleDerivation + +trait DerivedReducible[F[_]] extends Reducible[F] +object DerivedReducible extends DerivedReducibleLowPriority: + given delegated[F[_]](using F: => Reducible[F]): DerivedReducible[F] = + new Delegated(F) + + given composed[F[_]: DerivedReducible, G[_]: DerivedReducible]: DerivedReducible[[x] =>> F[G[x]]] = + new Delegated(Reducible[F].compose[G]) + + def product[F[_]](ev: Reducible[?])(using K1.ProductInstances[DerivedFoldable, F]): DerivedReducible[F] = + new Product[DerivedFoldable, F](ev) {} + + def coproduct[F[_]](using K1.CoproductInstances[DerivedReducible, F]): DerivedReducible[F] = + new Coproduct[DerivedReducible, F] {} + + trait Product[T[x[_]] <: Foldable[x], F[_]](ev: Reducible[?])(using inst: K1.ProductInstances[T, F]) + extends DerivedFoldable.Product[T, F], DerivedReducible[F]: + + private val evalNone = Eval.now(None) + + def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B = + inst.foldLeft[A, Option[B]](fa)(None)( + [f[_]] => (acc: Option[B], tf: T[f], fa: f[A]) => + acc match + case Some(b) => Continue(Some(tf.foldLeft(fa, b)(g))) + case None => Continue(tf.reduceLeftToOption(fa)(f)(g)) + ).get + + def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + inst.foldRight[A, Eval[Option[B]]](fa)(evalNone)( + [f[_]] => (tf: T[f], fa: f[A], acc: Eval[Option[B]]) => + Continue(acc.flatMap { + case Some(b) => tf.foldRight(fa, Eval.now(b))(g).map(Some.apply) + case None => tf.reduceRightToOption(fa)(f)(g) + }) + ).map(_.get) + + trait Coproduct[T[x[_]] <: Reducible[x], F[_]](using inst: K1.CoproductInstances[T, F]) + extends DerivedFoldable.Coproduct[T, F], DerivedReducible[F]: + + def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B = + inst.fold[A, B](fa) { [f[_]] => (tf: T[f], fa: f[A]) => + tf.reduceLeftTo(fa)(f)(g) + } + + def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + inst.fold[A, Eval[B]](fa) { [f[_]] => (tf: T[f], fa: f[A]) => + Eval.defer(tf.reduceRightTo(fa)(f)(g)) + } + + private final class Delegated[F[_]](F: => Reducible[F]) extends DerivedReducible[F]: + @threadUnsafe private lazy val underlying = F + export underlying._ + +private[derived] sealed abstract class DerivedReducibleLowPriority: + inline given derived[F[_]](using gen: K1.Generic[F]): DerivedReducible[F] = + inline gen match + case given K1.ProductGeneric[F] => + DerivedReducible.product(K1.summonFirst[DerivedReducible, gen.MirroredElemTypes, Const[Any]]) + case given K1.CoproductGeneric[F] => + DerivedReducible.coproduct + +trait ReducibleDerivation: + extension (F: Reducible.type) + def derived[F[_]](using instance: DerivedReducible[F]): Reducible[F] = instance diff --git a/core/src/main/scala-3/cats/derived/traverse.scala b/core/src/main/scala-3/cats/derived/traverse.scala index 4467d0a0..866666ba 100644 --- a/core/src/main/scala-3/cats/derived/traverse.scala +++ b/core/src/main/scala-3/cats/derived/traverse.scala @@ -2,32 +2,66 @@ package cats.derived import cats.{Applicative, Eval, Traverse} import shapeless3.deriving.{Const, Continue, K1} +import scala.annotation.threadUnsafe object traverse extends TraverseDerivation, Instances -trait ProductTraverse[T[x[_]] <: Traverse[x], F[_]](using inst: K1.ProductInstances[T, F]) - extends GenericFunctor[T, F], ProductFoldable[T, F], Traverse[F]: +trait DerivedTraverse[F[_]] extends Traverse[F] +object DerivedTraverse extends DerivedTraverseLowPriority: + given const[T]: DerivedTraverse[Const[T]] with + override def map[A, B](fa: T)(f: A => B): T = fa + 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 + def traverse[G[_], A, B](fa: T)(f: A => G[B])(using G: Applicative[G]): G[T] = G.pure(fa) - def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] = - inst.traverse[A, G, B](fa)([a,b] => (ga: G[a], f: a => b) => G.map(ga)(f))([a] => (x: a) => G.pure(x))([a,b] => (gf: G[a => b], ga: G[a]) => G.ap(gf)(ga))( - [t[_]] => (trav: T[t], t0: t[A]) => trav.traverse[G, A, B](t0)(f) - ) + given delegated[F[_]](using F: => Traverse[F]): DerivedTraverse[F] = + new Delegated(F) -trait CoproductTraverse[T[x[_]] <: Traverse[x], F[_]](using inst: K1.CoproductInstances[T, F]) - extends GenericFunctor[T, F], CoproductFoldable[T, F], Traverse[F]: + given composed[F[_]: DerivedTraverse, G[_]: DerivedTraverse]: DerivedTraverse[[x] =>> F[G[x]]] = + new Delegated(Traverse[F].compose[G]) - def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] = - inst.traverse[A, G, B](fa)([a,b] => (ga: G[a], f: a => b) => G.map(ga)(f))([a] => (x: a) => G.pure(x))([a,b] => (gf: G[a => b], ga: G[a]) => G.ap(gf)(ga))( - [t[_]] => (trav: T[t], t0: t[A]) => trav.traverse[G, A, B](t0)(f) - ) + def product[F[_]](using K1.ProductInstances[DerivedTraverse, F]): DerivedTraverse[F] = + new Product[DerivedTraverse, F] {} + + def coproduct[F[_]](using K1.CoproductInstances[DerivedTraverse, F]): DerivedTraverse[F] = + new Coproduct[DerivedTraverse, F] {} -trait TraverseDerivation: - extension (F: Traverse.type) - inline def derived[F[_]](using gen: K1.Generic[F]): Traverse[F] = - gen.derive(productTraverse, coproductTraverse) + trait Product[T[x[_]] <: Traverse[x], F[_]](using inst: K1.ProductInstances[T, F]) + extends DerivedFunctor.Generic[T, F], DerivedFoldable.Product[T, F], DerivedTraverse[F]: + + def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] = + inst.traverse[A, G, B](fa) { [a, b] => (ga: G[a], f: a => b) => + G.map(ga)(f) + } { [a] => (x: a) => + G.pure(x) + } { [a, b] => (gf: G[a => b], ga: G[a]) => + G.ap(gf)(ga) + } { [f[_]] => (tf: T[f], fa: f[A]) => + tf.traverse(fa)(f) + } + + trait Coproduct[T[x[_]] <: Traverse[x], F[_]](using inst: K1.CoproductInstances[T, F]) + extends DerivedFunctor.Generic[T, F], DerivedFoldable.Coproduct[T, F], DerivedTraverse[F]: - given productTraverse[F[_]](using inst: => K1.ProductInstances[Traverse, F]): Traverse[F] = - new ProductTraverse[Traverse, F]{} + def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] = + inst.traverse[A, G, B](fa) { [a, b] => (ga: G[a], f: a => b) => + G.map(ga)(f) + } { [a] => (x: a) => + G.pure(x) + } { [a, b] => (gf: G[a => b], ga: G[a]) => + G.ap(gf)(ga) + } { [f[_]] => (tf: T[f], fa: f[A]) => + tf.traverse(fa)(f) + } - given coproductTraverse[F[_]](using inst: => K1.CoproductInstances[Traverse, F]): Traverse[F] = - new CoproductTraverse[Traverse, F]{} + private final class Delegated[F[_]](F: => Traverse[F]) extends DerivedTraverse[F]: + @threadUnsafe private lazy val underlying = F + export underlying._ + +private[derived] sealed abstract class DerivedTraverseLowPriority: + inline given derived[F[_]](using gen: K1.Generic[F]): DerivedTraverse[F] = + gen.derive(DerivedTraverse.product, DerivedTraverse.coproduct) + +trait TraverseDerivation: + extension (F: Traverse.type) + def derived[F[_]](using instance: DerivedTraverse[F]): Traverse[F] = instance diff --git a/core/src/test/scala-3/cats/derived/FoldableTests.scala b/core/src/test/scala-3/cats/derived/FoldableTests.scala index 9d892b03..bd88fca4 100644 --- a/core/src/test/scala-3/cats/derived/FoldableTests.scala +++ b/core/src/test/scala-3/cats/derived/FoldableTests.scala @@ -2,7 +2,6 @@ package cats.derived import cats.Foldable import cats.derived.all._ -import cats.derived.all.given class FoldableTests { @@ -14,5 +13,5 @@ class FoldableTests { sealed trait CList[A] derives Foldable case object CNil extends CList[Nothing] - case class CCons[A](head: A, tail: CCons[A]) extends CList[A] + case class CCons[A](head: A, tail: CList[A]) extends CList[A] } diff --git a/core/src/test/scala-3/cats/derived/FunctorTests.scala b/core/src/test/scala-3/cats/derived/FunctorTests.scala index ed20a073..6943902a 100644 --- a/core/src/test/scala-3/cats/derived/FunctorTests.scala +++ b/core/src/test/scala-3/cats/derived/FunctorTests.scala @@ -2,7 +2,6 @@ package cats.derived import cats.Functor import cats.derived.all._ -import cats.derived.all.given class FunctorTests { @@ -14,5 +13,5 @@ class FunctorTests { sealed trait CList[A] derives Functor case object CNil extends CList[Nothing] - case class CCons[A](head: A, tail: CCons[A]) extends CList[A] + case class CCons[A](head: A, tail: CList[A]) extends CList[A] } diff --git a/core/src/test/scala-3/cats/derived/ReducibleTests.scala b/core/src/test/scala-3/cats/derived/ReducibleTests.scala new file mode 100644 index 00000000..d327918e --- /dev/null +++ b/core/src/test/scala-3/cats/derived/ReducibleTests.scala @@ -0,0 +1,31 @@ +package cats.derived + +import cats.Reducible +import cats.data.NonEmptyList +import cats.derived.all._ + +class ReducibleTests { + + case class Box[A](value: A) derives Reducible + + sealed trait OneOrMany[+A] derives Reducible + case class One[+A](value: A) extends OneOrMany[A] + case class Many[+A](values: NonEmptyList[A]) extends OneOrMany[A] + + sealed trait CList[A] derives Reducible + case class COne[A](value: A) extends CList[A] + case class CCons[A](head: A, tail: CList[A]) extends CList[A] + + case class NonEmptyTree[A](size: Int, value: A, tree: Tree[A]) + enum Tree[+A]: + case Leaf + case Node(left: Tree[A], value: A, right: Tree[A]) + + case class Foo[A](value: A, xs: List[A]) + enum MyList[+A]: + case Non + case Con(v: A, r: MyList[A]) + + summon[DerivedFunctor[MyList]] + summon[DerivedReducible[NonEmptyTree]] +} diff --git a/core/src/test/scala-3/cats/derived/TraverseTests.scala b/core/src/test/scala-3/cats/derived/TraverseTests.scala index 6652eef3..688408ed 100644 --- a/core/src/test/scala-3/cats/derived/TraverseTests.scala +++ b/core/src/test/scala-3/cats/derived/TraverseTests.scala @@ -2,7 +2,6 @@ package cats.derived import cats.Traverse import cats.derived.all._ -import cats.derived.all.given class TraverseTests { @@ -14,5 +13,5 @@ class TraverseTests { sealed trait CList[A] derives Traverse case object CNil extends CList[Nothing] - case class CCons[A](head: A, tail: CCons[A]) extends CList[A] + case class CCons[A](head: A, tail: CList[A]) extends CList[A] }