diff --git a/core/shared/src/main/scala-2/zio/prelude/newtypes/package.scala b/core/shared/src/main/scala-2/zio/prelude/newtypes/package.scala index 5ea00eec8..29a25aca3 100644 --- a/core/shared/src/main/scala-2/zio/prelude/newtypes/package.scala +++ b/core/shared/src/main/scala-2/zio/prelude/newtypes/package.scala @@ -132,4 +132,15 @@ package object newtypes { } type Natural = Natural.Type + + /** + * A newtype representing Right-to-left composition of functors. + * + * If F[_] and G[_] are both Covariant, then Nested[F, G, *] is also a Covariant + * If F[_] and G[_] are both IdentityBoth, then Nested[F, G, *] is also an IdentityBoth + * If F[_] and G[_] are both ForEach, then Nested[F, G, *] is also a ForEach + */ + object Nested extends NewtypeF + + type Nested[F[+_], G[+_], +A] = Nested.Type[F[G[A]]] } diff --git a/core/shared/src/main/scala-3/zio/prelude/newtypes/package.scala b/core/shared/src/main/scala-3/zio/prelude/newtypes/package.scala index 47a83b7d3..3ad5549dc 100644 --- a/core/shared/src/main/scala-3/zio/prelude/newtypes/package.scala +++ b/core/shared/src/main/scala-3/zio/prelude/newtypes/package.scala @@ -97,6 +97,16 @@ package object newtypes { type FailureOut[+A] = FailureOut.Type[A] + /** + * A newtype representing Right-to-left composition of functors. + * If F[_] and G[_] are both Covariant, then Nested[F, G, *] is also a Covariant + * If F[_] and G[_] are both IdentityBoth, then Nested[F, G, *] is also an IdentityBoth + * If F[_] and G[_] are both Traversable, then Nested[F, G, *] is also a Traversable + */ + object Nested extends NewtypeF + + type Nested[F[+_], G[+_], +A] = Nested.Type[F[G[A]]] + object Natural extends Subtype[Int] { override inline def assertion: Assertion[Int] = diff --git a/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala b/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala index 6e7a78482..43788b8b8 100644 --- a/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala +++ b/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala @@ -17,7 +17,7 @@ package zio.prelude import zio._ -import zio.prelude.newtypes.{AndF, Failure, OrF} +import zio.prelude.newtypes.{AndF, Failure, Nested, OrF} import zio.stm.ZSTM import zio.stream.{ZSink, ZStream} @@ -1053,7 +1053,7 @@ object AssociativeBoth { /** * The `IdentityBoth` instance for `Chunk`. */ - implicit val ChunkIdentityeBoth: IdentityBoth[Chunk] = + implicit val ChunkIdentityBoth: IdentityBoth[Chunk] = new IdentityBoth[Chunk] { def any: Chunk[Any] = Chunk.unit def both[A, B](fa: => Chunk[A], fb: => Chunk[B]): Chunk[(A, B)] = fa.flatMap(a => fb.map(b => (a, b))) @@ -1143,6 +1143,21 @@ object AssociativeBoth { def both[A, B](fa: => List[A], fb: => List[B]): List[(A, B)] = fa.flatMap(a => fb.map(b => (a, b))) } + /** + * The `IdentityBoth` (and `AssociativeBoth`) instance for `Nested[F, G, A]`. + */ + implicit def NestedIdentityBoth[F[+_]: IdentityBoth: Covariant, G[+_]](implicit + G: IdentityBoth[G] + ): IdentityBoth[({ type lambda[+A] = Nested[F, G, A] })#lambda] = + new IdentityBoth[({ type lambda[+A] = Nested[F, G, A] })#lambda] { + override def any: Nested[F, G, Any] = Nested(G.any.succeed[F]) + + override def both[A, B](fa: => Nested[F, G, A], fb: => Nested[F, G, B]): Nested[F, G, (A, B)] = + Nested { + Nested.unwrap[F[G[A]]](fa).zipWith(Nested.unwrap[F[G[B]]](fb))(_ zip _) + } + } + /** * The `AssociativeBoth` instance for `NonEmptyChunk`. */ diff --git a/core/shared/src/main/scala/zio/prelude/Covariant.scala b/core/shared/src/main/scala/zio/prelude/Covariant.scala index c13a7559e..94241dd93 100644 --- a/core/shared/src/main/scala/zio/prelude/Covariant.scala +++ b/core/shared/src/main/scala/zio/prelude/Covariant.scala @@ -16,6 +16,8 @@ package zio.prelude +import zio.prelude.newtypes.Nested + trait CovariantSubset[F[+_], Subset[_]] { def mapSubset[A, B: Subset](f: A => B): F[A] => F[B] } @@ -82,6 +84,22 @@ object Covariant { def apply[F[+_]](implicit covariant: Covariant[F]): Covariant[F] = covariant + /** + * Constructs the instance for `Covariant[({ type lambda[+A] = Nested[F, G, A] })#lambda]` + * given a pair of pre-existing `Covariant` instances for covariant, higher-kinded type + * parameters `F[+_]` and `G[+_]`. + */ + implicit def NestedCovariant[F[+_], G[+_]](implicit + F: Covariant[F], + G: Covariant[G] + ): Covariant[({ type lambda[+A] = Nested[F, G, A] })#lambda] = + new Covariant[({ type lambda[+A] = Nested[F, G, A] })#lambda] { + private lazy val composedCovariant = F.compose(G) + + override def map[A, B](f: A => B): Nested[F, G, A] => Nested[F, G, B] = { (x: Nested[F, G, A]) => + Nested(composedCovariant.map(f)(Nested.unwrap[F[G[A]]](x))) + } + } } trait CovariantSyntax { diff --git a/core/shared/src/main/scala/zio/prelude/Derive.scala b/core/shared/src/main/scala/zio/prelude/Derive.scala index 2db626675..0120691c1 100644 --- a/core/shared/src/main/scala/zio/prelude/Derive.scala +++ b/core/shared/src/main/scala/zio/prelude/Derive.scala @@ -16,6 +16,7 @@ package zio.prelude +import zio.prelude.newtypes.Nested import zio.{Cause, Chunk, Exit, NonEmptyChunk} import scala.util.Try @@ -56,15 +57,6 @@ object Derive { Equal.ChunkEqual } - /** - * The `DeriveEqual` instance for `List`. - */ - implicit val ListDeriveEqual: Derive[List, Equal] = - new Derive[List, Equal] { - def derive[A: Equal]: Equal[List[A]] = - Equal.ListEqual - } - /** * The `DeriveEqual` instance for `Either`. */ @@ -74,6 +66,23 @@ object Derive { Equal.EitherEqual } + /** + * The `DeriveEqual` instance for `Id`. + */ + implicit val IdDeriveEqual: Derive[Id, Equal] = + new Derive[Id, Equal] { + override def derive[A: Equal]: Equal[Id[A]] = Id.wrapAll(Equal[A]) + } + + /** + * The `DeriveEqual` instance for `List`. + */ + implicit val ListDeriveEqual: Derive[List, Equal] = + new Derive[List, Equal] { + def derive[A: Equal]: Equal[List[A]] = + Equal.ListEqual + } + /** * The `DeriveEqual` instance for `Map`. */ @@ -83,6 +92,18 @@ object Derive { Equal.MapPartialOrd } + /** + * The `DeriveEqual` instance for `Nested`. + */ + implicit def NestedDeriveEqual[F[+_], G[+_]](implicit + F: Derive[F, Equal], + G: Derive[G, Equal] + ): Derive[({ type lambda[A] = Nested[F, G, A] })#lambda, Equal] = + new Derive[({ type lambda[A] = Nested[F, G, A] })#lambda, Equal] { + override def derive[A: Equal]: Equal[Nested[F, G, A]] = + Equal.NestedEqual(F.derive(G.derive[A])) + } + /** * The `DeriveEqual` instance for `NonEmptyChunk`. */ diff --git a/core/shared/src/main/scala/zio/prelude/Equal.scala b/core/shared/src/main/scala/zio/prelude/Equal.scala index 32a72be81..984ba9921 100644 --- a/core/shared/src/main/scala/zio/prelude/Equal.scala +++ b/core/shared/src/main/scala/zio/prelude/Equal.scala @@ -19,6 +19,7 @@ package zio.prelude import zio.Exit.{Failure, Success} import zio.duration.{Duration => ZIODuration} import zio.prelude.coherent.{HashOrd, HashPartialOrd} +import zio.prelude.newtypes.Nested import zio.{Cause, Chunk, Exit, Fiber, NonEmptyChunk, ZTrace} import scala.annotation.implicitNotFound @@ -306,6 +307,11 @@ object Equal { implicit lazy val FiberIdHashOrd: Hash[Fiber.Id] with Ord[Fiber.Id] = HashOrd.derive[(Long, Long)].contramap[Fiber.Id](fid => (fid.startTimeMillis, fid.seqNumber)) + implicit def IdEqual[A: Equal]: Equal[Id[A]] = new Equal[Id[A]] { + override protected def checkEqual(l: Id[A], r: Id[A]): Boolean = + Id.unwrap[A](l) === Id.unwrap[A](r) + } + /** * `Hash` and `Ord` (and thus also `Equal`) instance for `Int` values. */ @@ -338,6 +344,12 @@ object Equal { l.forall { case (key, value) => r.get(key).fold(false)(_ === value) } } + implicit def NestedEqual[F[+_], G[+_], A](implicit eqFGA: Equal[F[G[A]]]): Equal[Nested[F, G, A]] = + new Equal[Nested[F, G, A]] { + override protected def checkEqual(l: Nested[F, G, A], r: Nested[F, G, A]): Boolean = + eqFGA.checkEqual(Nested.unwrap[F[G[A]]](l), Nested.unwrap[F[G[A]]](r)) + } + /** * Derives an `Equal[NonEmptyChunk[A]]` given an `Equal[A]`. */ diff --git a/laws/shared/src/main/scala/zio/prelude/laws/ForEachLaws.scala b/laws/shared/src/main/scala/zio/prelude/laws/ForEachLaws.scala index f0719fcd0..ad70ff79c 100644 --- a/laws/shared/src/main/scala/zio/prelude/laws/ForEachLaws.scala +++ b/laws/shared/src/main/scala/zio/prelude/laws/ForEachLaws.scala @@ -18,10 +18,17 @@ package zio.prelude.laws import zio.prelude._ import zio.prelude.coherent.DeriveEqualForEach +import zio.test.TestResult import zio.test.laws._ object ForEachLaws extends LawfulF.Covariant[DeriveEqualForEach, Equal] { + lazy val forEachIdentityLaw: LawsF.Covariant[DeriveEqualForEach, Equal] = + new LawsF.Covariant.Law1[DeriveEqualForEach, Equal]("forEachIdentityLaw") { + def apply[F[+_]: DeriveEqualForEach, A: Equal](fa: F[A]): TestResult = + fa.forEach(Id(_)) <-> Id(fa) + } + /** * The set of all laws that instances of `ForEach` must satisfy. */