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

Define laws for ForEach #679

Draft
wants to merge 9 commits into
base: series/1.x
Choose a base branch
from
11 changes: 11 additions & 0 deletions core/shared/src/main/scala-2/zio/prelude/newtypes/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]]]
}
10 changes: 10 additions & 0 deletions core/shared/src/main/scala-3/zio/prelude/newtypes/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand Down
19 changes: 17 additions & 2 deletions core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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`.
*/
Expand Down
18 changes: 18 additions & 0 deletions core/shared/src/main/scala/zio/prelude/Covariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
Expand Down Expand Up @@ -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 {
Expand Down
39 changes: 30 additions & 9 deletions core/shared/src/main/scala/zio/prelude/Derive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package zio.prelude

import zio.prelude.newtypes.Nested
import zio.{Cause, Chunk, Exit, NonEmptyChunk}

import scala.util.Try
Expand Down Expand Up @@ -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`.
*/
Expand All @@ -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`.
*/
Expand All @@ -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`.
*/
Expand Down
12 changes: 12 additions & 0 deletions core/shared/src/main/scala/zio/prelude/Equal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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]`.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down