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 Traversable #442

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 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
21 changes: 21 additions & 0 deletions core/shared/src/main/scala/zio/prelude/Derive.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package zio.prelude

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

import scala.util.Try
Expand Down Expand Up @@ -31,6 +32,26 @@ object Derive {
def apply[F[_], Typeclass[_]](implicit derive: Derive[F, Typeclass]): Derive[F, Typeclass] =
derive

/**
* 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 `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 `Chunk`.
*/
Expand Down
13 changes: 13 additions & 0 deletions core/shared/src/main/scala/zio/prelude/Equal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zio.prelude

import zio.Exit.{ Failure, Success }
import zio.prelude.coherent.HashOrd
import zio.prelude.newtypes.{ Nested, Product }
import zio.test.TestResult
import zio.test.laws.{ Lawful, Laws }
import zio.{ Cause, Chunk, Exit, Fiber, NonEmptyChunk, ZTrace }
Expand Down Expand Up @@ -204,6 +205,18 @@ object Equal extends Lawful[Equal] {
def default[A]: Equal[A] =
make(_ == _)

/** `Equal` instance for `Id` */
implicit def IdEqual[A: Equal]: Equal[Id[A]] = (l: Id[A], r: Id[A]) => Id.unwrap[A](l) === Id.unwrap[A](r)

/** Constructs an `Equal` instance for two nested type constructors. */
implicit def NestedEqual[F[+_], G[+_], A](implicit eq0: Equal[F[G[A]]]): Equal[Nested[F, G, A]] =
(l: Nested[F, G, A], r: Nested[F, G, A]) => eq0.checkEqual(Nested.unwrap[F[G[A]]](l), Nested.unwrap[F[G[A]]](r))

/** Constructs an `Equal` instance for the product of two type constructors. */
implicit def ProductEqual[F[+_], G[+_], A](implicit eq0: Equal[(F[A], G[A])]): Equal[Product[F, G, A]] =
(l: Product[F, G, A], r: Product[F, G, A]) =>
eq0.checkEqual(Product.unwrap[(F[A], G[A])](l), Product.unwrap[(F[A], G[A])](r))

/**
* `Hash` and `Ord` (and thus also `Equal`) instance for `Boolean` values.
*/
Expand Down
20 changes: 19 additions & 1 deletion core/shared/src/main/scala/zio/prelude/GenFs.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package zio.prelude

import zio.prelude.newtypes.Failure
import zio.prelude.newtypes.{ Failure, Nested }
import zio.random.Random
import zio.test.Gen.oneOf
import zio.test._
Expand Down Expand Up @@ -51,12 +51,30 @@ object GenFs {
oneOf(Gen.throwable.map(Future.failed), gen.map(Future.successful))
}

def id: GenF[Random with Sized, Id] = new GenF[Random with Sized, Id] {
def apply[R1 <: Random with Sized, A](gen: Gen[R1, A]): Gen[R1, Id[A]] =
gen.map(Id[A])
}

def map[R <: Random with Sized, K](k: Gen[R, K]): GenF[R, ({ type lambda[+v] = Map[K, v] })#lambda] =
new GenF[R, ({ type lambda[+v] = Map[K, v] })#lambda] {
def apply[R1 <: R, V](v: Gen[R1, V]): Gen[R1, Map[K, V]] =
Gen.mapOf(k, v)
}

def nested[F[+_], G[+_], RF, RG](
genF: GenF[RF, F],
genG: GenF[RG, G]
): GenF[RF with RG, ({ type lambda[+A] = Nested[F, G, A] })#lambda] =
new GenF[RF with RG, ({ type lambda[+A] = Nested[F, G, A] })#lambda] {
override def apply[R1 <: RF with RG, A](gen: Gen[R1, A]): Gen[R1, Nested[F, G, A]] = {
val value: Gen[R1 with RG with RF, newtypes.Nested.newtypeF.Type[F[G[A]]]] =
genF(genG(gen)).map(Nested(_): Nested[F, G, A])
value
}

}

/**
* A generator of `NonEmptyChunk` values.
*/
Expand Down
103 changes: 103 additions & 0 deletions core/shared/src/main/scala/zio/prelude/Instances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package zio.prelude

import zio.prelude.AssociativeBoth.IdIdentityBoth
import zio.prelude.Invariant.IdCovariant
import zio.prelude.classic.Applicative
import zio.prelude.newtypes.{ Nested, Product }

// not sure about putting this here
object Instances {

object Applicative {
def apply[F[+_]: Covariant: IdentityBoth]: Applicative[F] = new Covariant[F] with IdentityBoth[F] { self =>
def map[A, B](f: A => B): F[A] => F[B] = Covariant[F].map(f)

def any: F[Any] = IdentityBoth[F].any

def both[A, B](fa: => F[A], fb: => F[B]): F[(A, B)] = IdentityBoth[F].both(fa, fb)
}

final def compose[F[+_]: Applicative, G[+_]: Applicative](
F: Applicative[F],
G: Applicative[G]
): Applicative[({ type lambda[+A] = F[G[A]] })#lambda] =
new Covariant[({ type lambda[+A] = F[G[A]] })#lambda] with IdentityBoth[({ type lambda[+A] = F[G[A]] })#lambda] {
def map[A, B](f: A => B): F[G[A]] => F[G[B]] = F.map(G.map(f))

def any: F[G[Any]] = F.map[Any, G[Any]](_ => G.any)(F.any)

def both[A, B](fa: => F[G[A]], fb: => F[G[B]]): F[G[(A, B)]] =
F.map[(G[A], G[B]), G[(A, B)]] { case (ga, gb) => G.both(ga, gb) }(F.both(fa, fb))
}

implicit def nested0[F[+_], G[+_]](implicit
F: Applicative[F],
G: Applicative[G]
): Applicative[({ type lambda[+A] = Nested[F, G, A] })#lambda] =
new Covariant[({ type lambda[+A] = Nested[F, G, A] })#lambda]
with IdentityBoth[({ type lambda[+A] = Nested[F, G, A] })#lambda] {
private lazy val FG = F.compose(G)

override def map[A, B](f: A => B): Nested[F, G, A] => Nested[F, G, B] = (fga: Nested[F, G, A]) =>
Nested(FG.map(f)(Nested.unwrap[F[G[A]]](fga)))
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 _))
}

final def product[F[+_]: Applicative, G[+_]: Applicative](
F: Applicative[F],
G: Applicative[G]
): Applicative[({ type lambda[+A] = (F[A], G[A]) })#lambda] =
new Covariant[({ type lambda[+A] = (F[A], G[A]) })#lambda]
with IdentityBoth[({ type lambda[+A] = (F[A], G[A]) })#lambda] {
def map[A, B](f: A => B): ((F[A], G[A])) => (F[B], G[B]) =
(faga: (F[A], G[A])) => (faga._1.map(f), faga._2.map(f))

def any: (F[Any], G[Any]) = (F.any, G.any)

def both[A, B](faga: => (F[A], G[A]), fbgb: => (F[B], G[B])): (F[(A, B)], G[(A, B)]) =
(F.both(faga._1, fbgb._1), G.both(faga._2, fbgb._2))
}

implicit val applicativeId: Applicative[Id] = Applicative[Id](IdCovariant, IdIdentityBoth)

implicit def product0[F[+_], G[+_]](implicit
F: Applicative[F],
G: Applicative[G]
): Applicative[({ type lambda[+A] = Product[F, G, A] })#lambda] =
new Covariant[({ type lambda[+A] = Product[F, G, A] })#lambda]
with IdentityBoth[({ type lambda[+A] = Product[F, G, A] })#lambda] {
private lazy val FG = Applicative.product(F, G)

override def map[A, B](f: A => B): Product[F, G, A] => Product[F, G, B] = (fga: Product[F, G, A]) =>
Product(FG.map(f)(Product.unwrap(fga)))

override def any: Product[F, G, Any] = Product((F.any, G.any))

override def both[A, B](fga: => Product[F, G, A], fgb: => Product[F, G, B]): Product[F, G, (A, B)] =
Product(FG.both(Product.unwrap(fga), Product.unwrap(fgb)))
}

}

trait ApplicativeDeriveEqual[F[+_]] extends Covariant[F] with IdentityBoth[F] with DeriveEqual[F]

object ApplicativeDeriveEqual {
implicit def derive[F[+_]](implicit
applicative0: Applicative[F],
deriveEqual0: DeriveEqual[F]
): ApplicativeDeriveEqual[F] =
new ApplicativeDeriveEqual[F] {
def any: F[Any] =
applicative0.any
def both[A, B](fa: => F[A], fb: => F[B]): F[(A, B)] =
applicative0.both(fa, fb)
def derive[A: Equal]: Equal[F[A]] =
deriveEqual0.derive
def map[A, B](f: A => B): F[A] => F[B] =
applicative0.map(f)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package zio.prelude

import zio.prelude.coherent.DeriveEqualNonEmptyTraversable
import zio.prelude.laws._
import zio.prelude.newtypes.{ Max, Min }
import zio.test.laws._
import zio.{ ChunkBuilder, NonEmptyChunk }

/**
Expand Down Expand Up @@ -134,12 +134,14 @@ trait NonEmptyTraversable[F[+_]] extends Traversable[F] {
def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] =
reduceMapLeft(fa)(NonEmptyList.single)((as, a) => NonEmptyList.cons(a, as)).reverse
}
object NonEmptyTraversable extends LawfulF.Covariant[DeriveEqualNonEmptyTraversable, Equal] {

import zio.prelude.Instances.ApplicativeDeriveEqual
object NonEmptyTraversable extends LawfulF.Traversable[DeriveEqualNonEmptyTraversable, ApplicativeDeriveEqual, Equal] {

/**
* The set of all laws that instances of `NonEmptyTraversable` must satisfy.
*/
val laws: LawsF.Covariant[DeriveEqualNonEmptyTraversable, Equal] =
val laws: LawsF.Traversable[DeriveEqualNonEmptyTraversable, ApplicativeDeriveEqual, Equal] =
Traversable.laws

/**
Expand Down
95 changes: 90 additions & 5 deletions core/shared/src/main/scala/zio/prelude/Traversable.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package zio.prelude

import zio.prelude.coherent.DeriveEqualTraversable
import zio.prelude.newtypes.{ And, First, Max, Min, Or, Prod, Sum }
import zio.test.laws._
import zio.prelude.newtypes.{ And, First, Max, Min, Nested, Or, Prod, Sum }
import zio.test.TestResult
import zio.{ Chunk, ChunkBuilder, NonEmptyChunk }

/**
Expand Down Expand Up @@ -267,13 +267,98 @@ trait Traversable[F[+_]] extends Covariant[F] {
).runResult(0)
}

object Traversable extends LawfulF.Covariant[DeriveEqualTraversable, Equal] {
object Traversable
extends zio.prelude.laws.LawfulF.Traversable[DeriveEqualTraversable, Instances.ApplicativeDeriveEqual, Equal] {
import zio.prelude.Instances.ApplicativeDeriveEqual
import zio.prelude.classic.Applicative
import zio.prelude.Instances.Applicative
import zio.prelude.laws.ZLawsF

/** Traversing by `Id` is equivalent to mapping. */
val identityLaw: zio.test.laws.LawsF.Covariant[DeriveEqualTraversable, Equal] =
new ZLawsF.Covariant.MapLaw[DeriveEqualTraversable, Equal]("identityLaw") {
def apply[F[+_]: DeriveEqualTraversable, A: Equal, B: Equal](fa: F[A], f: A => B): TestResult =
Id.unwrap(fa.foreach(f.andThen(Id[B]))) <-> fa.map(f)
}

/** Two sequentially dependent effects can be fused into their composition */
val sequentialFusionLaw: zio.prelude.laws.LawsF.Traversable[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal] =
new ZLawsF.Traversable.SequentialFusionLaw[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal](
"sequentialFusionLaw"
) {
def apply[
F[+_]: DeriveEqualTraversable,
G[+_]: ApplicativeDeriveEqual,
H[+_]: ApplicativeDeriveEqual,
A,
B,
C: Equal
](fa: F[A], agb: A => G[B], bhc: B => H[C]): TestResult = {
implicit val GH: Applicative[({ type lambda[+A] = Nested[G, H, A] })#lambda] = Applicative.nested0[G, H]

val left: Nested[G, H, F[C]] =
Nested(fa.foreach(agb).map(_.foreach(bhc)))
val right: Nested[G, H, F[C]] =
fa.foreach(a => (GH.both(Nested(agb(a).map(bhc)): Nested[G, H, C], GH.any): Nested[G, H, (C, Any)]).map(_._1))
left <-> right
}
}

/** Traversal with `succeed` is the same as applying `succeed` directly */
val purityLaw: zio.prelude.laws.LawsF.Traversable[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal] =
new ZLawsF.Traversable.PurityLaw[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal]("purityLaw") {
def apply[F[+_]: DeriveEqualTraversable, G[+_]: ApplicativeDeriveEqual, A: Equal](fa: F[A]): TestResult =
fa.foreach(Applicative[G].any.as[A](_)) <-> Applicative[G].any.as(fa)
}

val naturalityLaw: zio.prelude.laws.LawsF.Traversable[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal] =
new ZLawsF.Traversable.NaturalityFusionLaw[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal]("naturalityLaw") {
def apply[F[+_]: DeriveEqualTraversable, G[+_]: ApplicativeDeriveEqual, H[+_]: ApplicativeDeriveEqual, A: Equal](
fa: F[A],
aga: A => G[A],
aha: A => H[A]
): TestResult = {
implicit val GH: Applicative[({ type lambda[+A] = Nested[G, H, A] })#lambda] = Applicative.nested0[G, H]

val left: Nested[G, H, F[A]] =
Nested(fa.map(aga).flip.map(a => a.map(aha).flip))
val right: Nested[G, H, F[A]] =
fa.map(a => (GH.both(Nested(aga(a).map(aha)), GH.any): Nested[G, H, (A, Any)]).map(_._1)).flip
left <-> right
}
}

/** Two independent effects can be fused into their product */
val parallelFusionLaw: zio.prelude.laws.LawsF.Traversable[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal] =
new ZLawsF.Traversable.ParallelFusionLaw[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal](
"parallelFusionLaw"
) {
def apply[
F[+_]: DeriveEqualTraversable,
G[+_]: ApplicativeDeriveEqual,
H[+_]: ApplicativeDeriveEqual,
A,
B: Equal
](
fa: F[A],
agb: A => G[B],
ahb: A => H[B]
): TestResult = {
import newtypes.Product
implicit val GH: Applicative[({ type lambda[+A] = Product[G, H, A] })#lambda] = Applicative.product0[G, H]

val left: Product[G, H, F[B]] = Product((fa.foreach(agb), fa.foreach(ahb)))
val right: Product[G, H, F[B]] =
fa.foreach(a => (GH.both(Product((agb(a), ahb(a))), GH.any): Product[G, H, (B, Any)]).map(_._1))
left <-> right
}
}

/**
* The set of all laws that instances of `Traversable` must satisfy.
*/
val laws: LawsF.Covariant[DeriveEqualTraversable, Equal] =
Covariant.laws
val laws: zio.prelude.laws.LawsF.Traversable[DeriveEqualTraversable, ApplicativeDeriveEqual, Equal] =
purityLaw + identityLaw + sequentialFusionLaw + naturalityLaw + parallelFusionLaw + Covariant.laws

/**
* Summons an implicit `Traversable`.
Expand Down
15 changes: 15 additions & 0 deletions core/shared/src/main/scala/zio/prelude/laws/ZLawfulF.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package zio.prelude.laws

object ZLawfulF {

/** `ZLawful` for traversable type constructors. */
trait Traversable[-CapsF[_[+_]], -CapsG[_[+_]], -Caps[_], -R] { self =>
def laws: ZLawsF.Traversable[CapsF, CapsG, Caps, R]
def +[CapsF1[x[+_]] <: CapsF[x], CapsG1[x[+_]] <: CapsG[x], Caps1[x] <: Caps[x], R1 <: R](
that: Traversable[CapsF1, CapsG1, Caps1, R1]
): Traversable[CapsF1, CapsG1, Caps1, R1] =
new Traversable[CapsF1, CapsG1, Caps1, R1] {
val laws = self.laws + that.laws
}
}
}
Loading