From 6ada8f23d80f4fd53956a1ad1b23ea71ff941173 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Sun, 12 May 2024 20:11:42 +0200 Subject: [PATCH] Bring Ring-like structures to ZIO Prelude (#351) --- build.sbt | 1 + .../test/scala/zio/prelude/CoherentSpec.scala | 6 +- .../zio/prelude/PartialInverseSpec.scala | 28 + .../main/scala/zio/prelude/Associative.scala | 148 ++- .../src/main/scala/zio/prelude/Inverse.scala | 15 +- .../scala/zio/prelude/PartialInverse.scala | 902 ++++++++++++++++++ .../scala/zio/prelude/coherent/coherent.scala | 14 +- .../src/main/scala/zio/prelude/package.scala | 1 + .../abstraction-diagrams.md | 31 +- .../experimental/laws/AnnihilationLaws.scala | 45 + .../laws/DistributiveProdLaws.scala | 45 + .../experimental/AnnihilationSpec.scala | 29 + .../experimental/DistributiveProdSpec.scala | 19 + .../prelude/experimental/ZIOBaseSpec.scala | 2 +- .../prelude/experimental/Annihilation.scala | 18 + .../experimental/DistributiveProd.scala | 166 ++++ .../zio/prelude/experimental/Divide.scala | 30 + .../prelude/experimental/PartialDivide.scala | 30 + .../zio/prelude/experimental/Subtract.scala | 29 + .../experimental/coherent/coherent.scala | 38 +- .../zio/prelude/experimental/package.scala | 6 +- .../scala/zio/prelude/laws/InverseLaws.scala | 2 +- .../zio/prelude/laws/PartialInverseLaws.scala | 32 + 23 files changed, 1577 insertions(+), 60 deletions(-) create mode 100644 core-tests/shared/src/test/scala/zio/prelude/PartialInverseSpec.scala create mode 100644 core/shared/src/main/scala/zio/prelude/PartialInverse.scala create mode 100644 experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/AnnihilationLaws.scala create mode 100644 experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/DistributiveProdLaws.scala create mode 100644 experimental-tests/shared/src/test/scala/zio/prelude/experimental/AnnihilationSpec.scala create mode 100644 experimental-tests/shared/src/test/scala/zio/prelude/experimental/DistributiveProdSpec.scala create mode 100644 experimental/shared/src/main/scala/zio/prelude/experimental/Annihilation.scala create mode 100644 experimental/shared/src/main/scala/zio/prelude/experimental/DistributiveProd.scala create mode 100644 experimental/shared/src/main/scala/zio/prelude/experimental/Divide.scala create mode 100644 experimental/shared/src/main/scala/zio/prelude/experimental/PartialDivide.scala create mode 100644 experimental/shared/src/main/scala/zio/prelude/experimental/Subtract.scala create mode 100644 laws/shared/src/main/scala/zio/prelude/laws/PartialInverseLaws.scala diff --git a/build.sbt b/build.sbt index d446150b7..e2e84fbd0 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,7 @@ import BuildHelper._ Global / onChangedBuildSource := ReloadOnSourceChanges +Global / concurrentRestrictions += Tags.limit(NativeTags.Link, 1) inThisBuild( List( diff --git a/core-tests/shared/src/test/scala/zio/prelude/CoherentSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/CoherentSpec.scala index e799ada1c..7b4a9c8f2 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/CoherentSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/CoherentSpec.scala @@ -45,9 +45,9 @@ object CoherentSpec extends ZIOBaseSpec { test("EqualInverse") { val instance = implicitly[EqualInverse[Sum[Int]]] assert(Sum(42).inverse(Sum(20)))(equalTo(Sum(22))) && - assert(Sum(2).multiply(5))(equalTo(Sum(10))) && - assert(Sum(2).multiply(0))(equalTo(Sum(0))) && - assert(Sum(2).multiply(-5))(equalTo(Sum(-10))) && + assert(Sum(2).multiplyBy(5))(equalTo(Sum(10))) && + assert(Sum(2).multiplyBy(0))(equalTo(Sum(0))) && + assert(Sum(2).multiplyBy(-5))(equalTo(Sum(-10))) && assert(instance.multiplyOption(5)(Sum(2)))(equalTo[Option[Sum[Int]]](Some(Sum(10)))) && assert(instance.multiplyOption(0)(Sum(2)))(equalTo[Option[Sum[Int]]](Some(Sum(0)))) && assert(instance.multiplyOption(-5)(Sum(2)))(equalTo[Option[Sum[Int]]](Some(Sum(-10)))) diff --git a/core-tests/shared/src/test/scala/zio/prelude/PartialInverseSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/PartialInverseSpec.scala new file mode 100644 index 000000000..e4a9101e7 --- /dev/null +++ b/core-tests/shared/src/test/scala/zio/prelude/PartialInverseSpec.scala @@ -0,0 +1,28 @@ +package zio.prelude + +import zio.prelude.laws._ +import zio.prelude.newtypes.Prod +import zio.test._ +import zio.test.laws._ + +object PartialInverseSpec extends ZIOBaseSpec { + + private implicit val DoubleEqual: Equal[Double] = Equal.DoubleEqualWithEpsilon() + private implicit val FloatEqual: Equal[Float] = Equal.FloatEqualWithEpsilon() + + def spec: Spec[Environment, Any] = + suite("PartialInverseSpec")( + suite("laws")( + test("BigDecimal prod")( + checkAllLaws(PartialInverseLaws)(Gen.bigDecimal(BigDecimal(-10), BigDecimal(10)).map(Prod(_))) + ), + test("byte prod")(checkAllLaws(PartialInverseLaws)(Gen.byte.map(Prod(_)))), + test("char prod")(checkAllLaws(PartialInverseLaws)(Gen.char.map(Prod(_)))), + test("double prod")(checkAllLaws(PartialInverseLaws)(Gen.double.map(Prod(_)))), + test("float prod")(checkAllLaws(PartialInverseLaws)(Gen.float.map(Prod(_)))), + test("int prod")(checkAllLaws(PartialInverseLaws)(Gen.int.map(Prod(_)))), + test("long prod")(checkAllLaws(PartialInverseLaws)(Gen.long.map(Prod(_)))), + test("short prod")(checkAllLaws(PartialInverseLaws)(Gen.short.map(Prod(_)))) + ) + ) +} diff --git a/core/shared/src/main/scala/zio/prelude/Associative.scala b/core/shared/src/main/scala/zio/prelude/Associative.scala index d1fbb67de..1d02a7641 100644 --- a/core/shared/src/main/scala/zio/prelude/Associative.scala +++ b/core/shared/src/main/scala/zio/prelude/Associative.scala @@ -183,19 +183,25 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative`, `Idempotent` instance for the product of `BigDecimal` values + * The `Commutative`, `PartialInverse` instance for the product of `BigDecimal` values */ - implicit val BigDecimalProdCommutativeIdempotent: Commutative[Prod[BigDecimal]] with Idempotent[Prod[BigDecimal]] = - new Commutative[Prod[BigDecimal]] with Idempotent[Prod[BigDecimal]] { - override def combine(l: => Prod[BigDecimal], r: => Prod[BigDecimal]): Prod[BigDecimal] = Prod(l * r) + implicit val BigDecimalProdCommutativePartialInverse + : Commutative[Prod[BigDecimal]] with PartialInverse[Prod[BigDecimal]] = + new Commutative[Prod[BigDecimal]] with PartialInverse[Prod[BigDecimal]] { + def combine(l: => Prod[BigDecimal], r: => Prod[BigDecimal]): Prod[BigDecimal] = Prod(l * r) + val identity: Prod[BigDecimal] = Prod(BigDecimal(1)) + def inverseOption(l: => Prod[BigDecimal], r: => Prod[BigDecimal]): Option[Prod[BigDecimal]] = + if (r != BigDecimal(0)) Some(Prod(l / r)) else None } /** * The `Commutative`, `Idempotent` instance for the sum of `BigDecimal` values */ - implicit val BigDecimalSumCommutativeIdempotent: Commutative[Sum[BigDecimal]] with Idempotent[Sum[BigDecimal]] = - new Commutative[Sum[BigDecimal]] with Idempotent[Sum[BigDecimal]] { - override def combine(l: => Sum[BigDecimal], r: => Sum[BigDecimal]): Sum[BigDecimal] = Sum(l + r) + implicit val BigDecimalSumCommutativeInverse: Commutative[Sum[BigDecimal]] with Inverse[Sum[BigDecimal]] = + new Commutative[Sum[BigDecimal]] with Inverse[Sum[BigDecimal]] { + def combine(l: => Sum[BigDecimal], r: => Sum[BigDecimal]): Sum[BigDecimal] = Sum(l + r) + val identity: Sum[BigDecimal] = Sum(BigDecimal(0)) + def inverse(l: => Sum[BigDecimal], r: => Sum[BigDecimal]): Sum[BigDecimal] = Sum(l - r) } /** @@ -255,13 +261,15 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative` and `Identity` instance for the product of `Byte` + * The `Commutative` and `PartialInverse` instance for the product of `Byte` * values. */ - implicit val ByteProdCommutativeIdentity: Commutative[Prod[Byte]] with Identity[Prod[Byte]] = - new Commutative[Prod[Byte]] with Identity[Prod[Byte]] { - def combine(l: => Prod[Byte], r: => Prod[Byte]): Prod[Byte] = Prod((l * r).toByte) - val identity: Prod[Byte] = Prod(1) + implicit val ByteProdCommutativePartialInverse: Commutative[Prod[Byte]] with PartialInverse[Prod[Byte]] = + new Commutative[Prod[Byte]] with PartialInverse[Prod[Byte]] { + def combine(l: => Prod[Byte], r: => Prod[Byte]): Prod[Byte] = Prod((l * r).toByte) + val identity: Prod[Byte] = Prod(1) + def inverseOption(l: => Prod[Byte], r: => Prod[Byte]): Option[Prod[Byte]] = + if (r != 0) Some(Prod((l / r).toByte)) else None } /** @@ -291,13 +299,15 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative` and `Identity` instance for the product of `Char` + * The `Commutative` and `PartialInverse` instance for the product of `Char` * values. */ - implicit val CharProdCommutativeIdentity: Commutative[Prod[Char]] with Identity[Prod[Char]] = - new Commutative[Prod[Char]] with Identity[Prod[Char]] { - def combine(l: => Prod[Char], r: => Prod[Char]): Prod[Char] = Prod((l * r).toChar) - val identity: Prod[Char] = Prod(1) + implicit val CharProdCommutativePartialInverse: Commutative[Prod[Char]] with PartialInverse[Prod[Char]] = + new Commutative[Prod[Char]] with PartialInverse[Prod[Char]] { + def combine(l: => Prod[Char], r: => Prod[Char]): Prod[Char] = Prod((l * r).toChar) + val identity: Prod[Char] = Prod(1) + override def inverseOption(l: => Prod[Char], r: => Prod[Char]): Option[Prod[Char]] = + if (r != 0) Some(Prod((l / r).toChar)) else None } /** @@ -369,13 +379,15 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative` and `Identity` instance for the product of `Double` + * The `Commutative` and `PartialInverse` instance for the product of `Double` * values. */ - implicit val DoubleProdCommutativeIdentity: Commutative[Prod[Double]] with Identity[Prod[Double]] = - new Commutative[Prod[Double]] with Identity[Prod[Double]] { - def combine(l: => Prod[Double], r: => Prod[Double]): Prod[Double] = Prod(l * r) - val identity: Prod[Double] = Prod(1) + implicit val DoubleProdCommutativePartialInverse: Commutative[Prod[Double]] with PartialInverse[Prod[Double]] = + new Commutative[Prod[Double]] with PartialInverse[Prod[Double]] { + def combine(l: => Prod[Double], r: => Prod[Double]): Prod[Double] = Prod(l * r) + val identity: Prod[Double] = Prod(1) + def inverseOption(l: => Prod[Double], r: => Prod[Double]): Option[Prod[Double]] = + if (r != 0) Some(Prod(l / r)) else None } /** @@ -431,13 +443,15 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative` and `Identity` instance for the product of `Float` + * The `Commutative` and `PartialInverse` instance for the product of `Float` * values. */ - implicit val FloatProdCommutativeIdentity: Commutative[Prod[Float]] with Identity[Prod[Float]] = - new Commutative[Prod[Float]] with Identity[Prod[Float]] { - def combine(l: => Prod[Float], r: => Prod[Float]): Prod[Float] = Prod(l * r) - val identity: Prod[Float] = Prod(1) + implicit val FloatProdCommutativePartialInverse: Commutative[Prod[Float]] with PartialInverse[Prod[Float]] = + new Commutative[Prod[Float]] with PartialInverse[Prod[Float]] { + def combine(l: => Prod[Float], r: => Prod[Float]): Prod[Float] = Prod(l * r) + val identity: Prod[Float] = Prod(1) + def inverseOption(l: => Prod[Float], r: => Prod[Float]): Option[Prod[Float]] = + if (r != 0) Some(Prod(l / r)) else None } /** @@ -501,13 +515,15 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative` and `Identity` instance for the product of `Long` + * The `Commutative` and `PartialInverse` instance for the product of `Long` * values. */ - implicit val LongProdCommutativeIdentity: Commutative[Prod[Long]] with Identity[Prod[Long]] = - new Commutative[Prod[Long]] with Identity[Prod[Long]] { - def combine(l: => Prod[Long], r: => Prod[Long]): Prod[Long] = Prod(l * r) - val identity: Prod[Long] = Prod(1) + implicit val LongProdCommutativePartialInverse: Commutative[Prod[Long]] with PartialInverse[Prod[Long]] = + new Commutative[Prod[Long]] with PartialInverse[Prod[Long]] { + def combine(l: => Prod[Long], r: => Prod[Long]): Prod[Long] = Prod(l * r) + val identity: Prod[Long] = Prod(1) + def inverseOption(l: => Prod[Long], r: => Prod[Long]): Option[Prod[Long]] = + if (r != 0) Some(Prod(l / r)) else None } /** @@ -587,6 +603,21 @@ object Associative extends AssociativeLowPriority { } ) + implicit def ParSeqProdIdentity[A]: Identity[Prod[ParSeq[Unit, A]]] = new Identity[Prod[ParSeq[Unit, A]]] { + def identity: Prod[ParSeq[Unit, A]] = + Prod(ParSeq.empty) + def combine(l: => Prod[ParSeq[Unit, A]], r: => Prod[ParSeq[Unit, A]]): Prod[ParSeq[Unit, A]] = + Prod(Prod.unwrap(l) ++ Prod.unwrap(r)) + } + + implicit def ParSeqSumCommutativeIdentity[A]: Commutative[Sum[ParSeq[Unit, A]]] with Identity[Sum[ParSeq[Unit, A]]] = + new Commutative[Sum[ParSeq[Unit, A]]] with Identity[Sum[ParSeq[Unit, A]]] { + def identity: Sum[ParSeq[Unit, A]] = + Sum(ParSeq.empty) + def combine(l: => Sum[ParSeq[Unit, A]], r: => Sum[ParSeq[Unit, A]]): Sum[ParSeq[Unit, A]] = + Sum(Sum.unwrap(l) && Sum.unwrap(r)) + } + /** * The `Commutative` and `Idempotent` instance for the intersection of `Set[A]` values. */ @@ -623,19 +654,21 @@ object Associative extends AssociativeLowPriority { } /** - * The `Commutative` and `Identity` instance for the product of `Short` + * The `Commutative` and `PartialInverse` instance for the product of `Short` * values. */ - implicit val ShortProdCommutativeIdentity: Commutative[Prod[Short]] with Identity[Prod[Short]] = - new Commutative[Prod[Short]] with Identity[Prod[Short]] { - def combine(l: => Prod[Short], r: => Prod[Short]): Prod[Short] = Prod((l * r).toShort) - val identity: Prod[Short] = Prod(1) + implicit val ShortProdCommutativePartialInverse: Commutative[Prod[Short]] with PartialInverse[Prod[Short]] = + new Commutative[Prod[Short]] with PartialInverse[Prod[Short]] { + def combine(l: => Prod[Short], r: => Prod[Short]): Prod[Short] = Prod((l * r).toShort) + val identity: Prod[Short] = Prod(1) + def inverseOption(l: => Prod[Short], r: => Prod[Short]): Option[Prod[Short]] = + if (r != 0) Some(Prod((l / r).toShort)) else None } /** - * The `Commutative` and `Identity` instance for the sum of `Short` values. + * The `Commutative` and `Inverse` instance for the sum of `Short` values. */ - implicit val ShortSumCommutativeIdentity: Commutative[Sum[Short]] with Inverse[Sum[Short]] = + implicit val ShortSumCommutativeInverse: Commutative[Sum[Short]] with Inverse[Sum[Short]] = new Commutative[Sum[Short]] with Inverse[Sum[Short]] { def combine(l: => Sum[Short], r: => Sum[Short]): Sum[Short] = Sum((l + r).toShort) val identity: Sum[Short] = Sum(0) @@ -1388,17 +1421,44 @@ object Associative extends AssociativeLowPriority { */ implicit def VectorIdentity[A]: Identity[Vector[A]] = Identity.make(Vector.empty, _ ++ _) + + implicit def zioCauseProdIdentity[A]: Identity[Prod[zio.Cause[A]]] = new Identity[Prod[zio.Cause[A]]] { + def identity: Prod[zio.Cause[A]] = + Prod(zio.Cause.empty) + def combine(l: => Prod[zio.Cause[A]], r: => Prod[zio.Cause[A]]): Prod[zio.Cause[A]] = + Prod(Prod.unwrap(l) ++ Prod.unwrap(r)) + } + + implicit def zioCauseSumCommutativeIdentity[A]: Commutative[Sum[zio.Cause[A]]] with Identity[Sum[zio.Cause[A]]] = + new Commutative[Sum[zio.Cause[A]]] with Identity[Sum[zio.Cause[A]]] { + def identity: Sum[zio.Cause[A]] = + Sum(zio.Cause.empty) + def combine(l: => Sum[zio.Cause[A]], r: => Sum[zio.Cause[A]]): Sum[zio.Cause[A]] = + Sum(Sum.unwrap(l) && Sum.unwrap(r)) + } } trait AssociativeLowPriority { + implicit def FxCauseProdAssociative[A]: Associative[Prod[fx.Cause[A]]] = new Associative[Prod[fx.Cause[A]]] { + def combine(l: => Prod[fx.Cause[A]], r: => Prod[fx.Cause[A]]): Prod[fx.Cause[A]] = + Prod(Prod.unwrap(l) ++ Prod.unwrap(r)) + } + + implicit def FxCauseSumCommutative[A]: Commutative[Sum[fx.Cause[A]]] = new Commutative[Sum[fx.Cause[A]]] { + def combine(l: => Sum[fx.Cause[A]], r: => Sum[fx.Cause[A]]): Sum[fx.Cause[A]] = + Sum(Sum.unwrap(l) && Sum.unwrap(r)) + } + /** - * The `Commutative` and `Identity` instance for the product of `Int` values. + * The `Commutative` and `PartialInverse` instance for the product of `Int` values. */ - implicit val IntProdCommutativeIdentity: Commutative[Prod[Int]] with Identity[Prod[Int]] = - new Commutative[Prod[Int]] with Identity[Prod[Int]] { - def combine(l: => Prod[Int], r: => Prod[Int]): Prod[Int] = Prod(l * r) - val identity: Prod[Int] = Prod(1) + implicit val IntProdCommutativePartialInverse: Commutative[Prod[Int]] with PartialInverse[Prod[Int]] = + new Commutative[Prod[Int]] with PartialInverse[Prod[Int]] { + def combine(l: => Prod[Int], r: => Prod[Int]): Prod[Int] = Prod(l * r) + val identity: Prod[Int] = Prod(1) + def inverseOption(l: => Prod[Int], r: => Prod[Int]): Option[Prod[Int]] = + if (r != 0) Some(Prod(l / r)) else None } /** diff --git a/core/shared/src/main/scala/zio/prelude/Inverse.scala b/core/shared/src/main/scala/zio/prelude/Inverse.scala index f31dbeea4..4794fe7b3 100644 --- a/core/shared/src/main/scala/zio/prelude/Inverse.scala +++ b/core/shared/src/main/scala/zio/prelude/Inverse.scala @@ -34,10 +34,13 @@ import scala.annotation.tailrec * natural numbers, since subtracting a number from itself always returns * zero. */ -trait Inverse[A] extends Identity[A] { +trait Inverse[A] extends PartialInverse[A] { + def inverse(l: => A, r: => A): A - def multiply(n: Int)(a: A): A = { + final override def inverseOption(l: => A, r: => A): Some[A] = Some(inverse(l, r)) + + def multiplyBy(n: Int)(a: A): A = { @tailrec def multiplyHelper(res: A, n: Int): A = if (n == 0) res @@ -46,8 +49,8 @@ trait Inverse[A] extends Identity[A] { multiplyHelper(identity, n) } - override def multiplyOption(n: Int)(a: A): Some[A] = - Some(multiply(n)(a)) + final override def multiplyOption(n: Int)(a: A): Some[A] = + Some(multiplyBy(n)(a)) } object Inverse { @@ -903,7 +906,7 @@ trait InverseSyntax { /** * Multiplies value 'n' times */ - def multiply(n: Int)(implicit inverse: Inverse[A]): A = - inverse.multiply(n)(l) + def multiplyBy(n: Int)(implicit inverse: Inverse[A]): A = + inverse.multiplyBy(n)(l) } } diff --git a/core/shared/src/main/scala/zio/prelude/PartialInverse.scala b/core/shared/src/main/scala/zio/prelude/PartialInverse.scala new file mode 100644 index 000000000..f4040c1b4 --- /dev/null +++ b/core/shared/src/main/scala/zio/prelude/PartialInverse.scala @@ -0,0 +1,902 @@ +package zio.prelude + +import scala.annotation.tailrec + +/** + * The `PartialInverse` type class describes an associative binary operator for a + * type `A` that has an identity element and an inverse binary operator. + * Combining any value with itself with the inverse operator must return the + * identity element. There may be an exception, where the result is not defined. + * For example, for multiplication of reals, one is an identity + * element and division is an inverse operation, because dividing any + * value by itself always returns one. + * + * Because `PartialInverse` defines a binary rather than a unary operator it can be + * used to describe inverse operations for types that do not have inverse + * values. For example, the natural numbers do not have inverses because the + * set of natural numbers does not include negative numbers. But we can still + * define a subtraction operation that is the inverse of addition for the + * natural numbers, since subtracting a number from itself always returns + * zero. + */ +trait PartialInverse[A] extends Identity[A] { + + def inverseOption(l: => A, r: => A): Option[A] + + override def multiplyOption(n: Int)(a: A): Option[A] = { + @tailrec + def multiplyHelper(res: Option[A], n: Int): Option[A] = res match { + case Some(res) => + if (n == 0) Some(res) + else if (n > 0) multiplyHelper(Some(combine(a, res)), n - 1) + else multiplyHelper(inverseOption(res, a), n + 1) + case _ => res + } + + multiplyHelper(Some(identity), n) + } +} + +object PartialInverse { + + /** + * Summons an implicit `PartialInverse[A]`. + */ + def apply[A](implicit PartialInverse: PartialInverse[A]): PartialInverse[A] = PartialInverse + + /** + * Constructs an `PartialInverse` instance from an associative binary operator, an + * identity element, and an inverse binary operator. + */ + def make[A](identity0: A, op: (A, A) => A, inv: (A, A) => Option[A]): PartialInverse[A] = + new PartialInverse[A] { + def identity: A = identity0 + def combine(l: => A, r: => A): A = op(l, r) + def inverseOption(l: => A, r: => A): Option[A] = inv(l, r) + } + + /** + * Constructs an `PartialInverse` instance from an identity instance and + * an inverse function. + */ + def makeFrom[A](identity: Identity[A], inverse: (A, A) => Option[A]): PartialInverse[A] = + make(identity.identity, (l, r) => identity.combine(l, r), inverse) + + /** + * Derives an `PartialInverse[F[A]]` given a `Derive[F, PartialInverse]` and an + * `PartialInverse[A]`. + */ + implicit def DerivePartialInverse[F[_], A](implicit + derive: Derive[F, PartialInverse], + inverse: PartialInverse[A] + ): PartialInverse[F[A]] = + derive.derive(inverse) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple2PartialInverse[A: PartialInverse, B: PartialInverse]: PartialInverse[(A, B)] = + makeFrom( + Identity.Tuple2Identity, + { case ((a1, b1), (a2, b2)) => + (a1 ~?~ a2, b1 ~?~ b2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple3PartialInverse[A: PartialInverse, B: PartialInverse, C: PartialInverse] + : PartialInverse[(A, B, C)] = + makeFrom( + Identity.Tuple3Identity, + { case ((a1, b1, c1), (a2, b2, c2)) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple4PartialInverse[A: PartialInverse, B: PartialInverse, C: PartialInverse, D: PartialInverse] + : PartialInverse[(A, B, C, D)] = + makeFrom( + Identity.Tuple4Identity, + { case ((a1, b1, c1, d1), (a2, b2, c2, d2)) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2, d1 ~?~ d2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple5PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse + ]: PartialInverse[(A, B, C, D, E)] = + makeFrom( + Identity.Tuple5Identity, + { case ((a1, b1, c1, d1, e1), (a2, b2, c2, d2, e2)) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2, d1 ~?~ d2, e1 ~?~ e2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple6PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F)] = + makeFrom( + Identity.Tuple6Identity, + { + case ( + (a1, b1, c1, d1, e1, f1), + (a2, b2, c2, d2, e2, f2) + ) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2, d1 ~?~ d2, e1 ~?~ e2, f1 ~?~ f2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple7PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G)] = + makeFrom( + Identity.Tuple7Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1), + (a2, b2, c2, d2, e2, f2, g2) + ) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2, d1 ~?~ d2, e1 ~?~ e2, f1 ~?~ f2, g1 ~?~ g2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple8PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H)] = + makeFrom( + Identity.Tuple8Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1), + (a2, b2, c2, d2, e2, f2, g2, h2) + ) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2, d1 ~?~ d2, e1 ~?~ e2, f1 ~?~ f2, g1 ~?~ g2, h1 ~?~ h2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple9PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I)] = + makeFrom( + Identity.Tuple9Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2) + ) => + (a1 ~?~ a2, b1 ~?~ b2, c1 ~?~ c2, d1 ~?~ d2, e1 ~?~ e2, f1 ~?~ f2, g1 ~?~ g2, h1 ~?~ h2, i1 ~?~ i2).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple10PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J)] = + makeFrom( + Identity.Tuple10Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple11PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K)] = + makeFrom( + Identity.Tuple11Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple12PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L)] = + makeFrom( + Identity.Tuple12Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple13PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M)] = + makeFrom( + Identity.Tuple13Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple14PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N)] = + makeFrom( + Identity.Tuple14Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple15PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O)] = + makeFrom( + Identity.Tuple15Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple16PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P)] = + makeFrom( + Identity.Tuple16Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple17PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse, + Q: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q)] = + makeFrom( + Identity.Tuple17Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2, + q1 ~?~ q2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple18PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse, + Q: PartialInverse, + R: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R)] = + makeFrom( + Identity.Tuple18Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2, + q1 ~?~ q2, + r1 ~?~ r2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple19PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse, + Q: PartialInverse, + R: PartialInverse, + S: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S)] = + makeFrom( + Identity.Tuple19Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2, s2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2, + q1 ~?~ q2, + r1 ~?~ r2, + s1 ~?~ s2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple20PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse, + Q: PartialInverse, + R: PartialInverse, + S: PartialInverse, + T: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T)] = + makeFrom( + Identity.Tuple20Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2, s2, t2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2, + q1 ~?~ q2, + r1 ~?~ r2, + s1 ~?~ s2, + t1 ~?~ t2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple21PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse, + Q: PartialInverse, + R: PartialInverse, + S: PartialInverse, + T: PartialInverse, + U: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U)] = + makeFrom( + Identity.Tuple21Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2, s2, t2, u2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2, + q1 ~?~ q2, + r1 ~?~ r2, + s1 ~?~ s2, + t1 ~?~ t2, + u1 ~?~ u2 + ).tupleN + } + ) + + /** + * Derives an `PartialInverse` for a product type given an `PartialInverse` for + * each element of the product type. + */ + implicit def Tuple22PartialInverse[ + A: PartialInverse, + B: PartialInverse, + C: PartialInverse, + D: PartialInverse, + E: PartialInverse, + F: PartialInverse, + G: PartialInverse, + H: PartialInverse, + I: PartialInverse, + J: PartialInverse, + K: PartialInverse, + L: PartialInverse, + M: PartialInverse, + N: PartialInverse, + O: PartialInverse, + P: PartialInverse, + Q: PartialInverse, + R: PartialInverse, + S: PartialInverse, + T: PartialInverse, + U: PartialInverse, + V: PartialInverse + ]: PartialInverse[(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V)] = + makeFrom( + Identity.Tuple22Identity, + { + case ( + (a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1, q1, r1, s1, t1, u1, v1), + (a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2, s2, t2, u2, v2) + ) => + ( + a1 ~?~ a2, + b1 ~?~ b2, + c1 ~?~ c2, + d1 ~?~ d2, + e1 ~?~ e2, + f1 ~?~ f2, + g1 ~?~ g2, + h1 ~?~ h2, + i1 ~?~ i2, + j1 ~?~ j2, + k1 ~?~ k2, + l1 ~?~ l2, + m1 ~?~ m2, + n1 ~?~ n2, + o1 ~?~ o2, + p1 ~?~ p2, + q1 ~?~ q2, + r1 ~?~ r2, + s1 ~?~ s2, + t1 ~?~ t2, + u1 ~?~ u2, + v1 ~?~ v2 + ).tupleN + } + ) + +} + +trait PartialInverseSyntax { + + /** + * Provides infix syntax for combining two values with an inverse + * operation. + */ + implicit class PartialInverseOps[A](l: A) { + + /** + * A symbolic alias for `inverse`. + */ + def ~?~(r: => A)(implicit inverse: PartialInverse[A]): Option[A] = + inverse.inverseOption(l, r) + + /** + * PartialInverses this value with the specified value + */ + def inverseOption(r: => A)(implicit inverse: PartialInverse[A]): Option[A] = + inverse.inverseOption(l, r) + + } +} diff --git a/core/shared/src/main/scala/zio/prelude/coherent/coherent.scala b/core/shared/src/main/scala/zio/prelude/coherent/coherent.scala index 556a3c933..aba437488 100644 --- a/core/shared/src/main/scala/zio/prelude/coherent/coherent.scala +++ b/core/shared/src/main/scala/zio/prelude/coherent/coherent.scala @@ -311,7 +311,7 @@ object EqualIdempotent { } } -trait EqualInverse[A] extends EqualIdentity[A] with Inverse[A] +trait EqualInverse[A] extends EqualPartialInverse[A] with Inverse[A] object EqualInverse { implicit def derive[A](implicit equal0: Equal[A], inverse0: Inverse[A]): EqualInverse[A] = @@ -323,6 +323,18 @@ object EqualInverse { } } +trait EqualPartialInverse[A] extends EqualIdentity[A] with PartialInverse[A] + +object EqualPartialInverse { + implicit def derive[A](implicit equal0: Equal[A], inverse0: PartialInverse[A]): EqualPartialInverse[A] = + new EqualPartialInverse[A] { + def combine(l: => A, r: => A): A = inverse0.combine(l, r) + def identity: A = inverse0.identity + def inverseOption(l: => A, r: => A): Option[A] = inverse0.inverseOption(l, r) + override protected def checkEqual(l: A, r: A): Boolean = equal0.equal(l, r) + } +} + trait HashPartialOrd[-A] extends Hash[A] with PartialOrd[A] { self => override def contramap[B](f: B => A): Hash[B] with PartialOrd[B] = new HashPartialOrd[B] { diff --git a/core/shared/src/main/scala/zio/prelude/package.scala b/core/shared/src/main/scala/zio/prelude/package.scala index dcae6664f..47be34c5e 100644 --- a/core/shared/src/main/scala/zio/prelude/package.scala +++ b/core/shared/src/main/scala/zio/prelude/package.scala @@ -46,6 +46,7 @@ package object prelude with NonEmptyListSyntax with NonEmptySetSyntax with OrdSyntax + with PartialInverseSyntax with PartialOrdSyntax with ZNonEmptySetSyntax with ZSetSyntax diff --git a/docs/functional-abstractions/abstraction-diagrams.md b/docs/functional-abstractions/abstraction-diagrams.md index cb005f15b..13f6ad2c2 100644 --- a/docs/functional-abstractions/abstraction-diagrams.md +++ b/docs/functional-abstractions/abstraction-diagrams.md @@ -42,7 +42,8 @@ classDiagram Associative~A~ <|-- Commutative~A~ Associative~A~ <|-- Idempotent~A~ Associative~A~ <|-- Identity~A~ - Identity~A~ <|-- Inverse~A~ + Identity~A~ <|-- PartialInverse~A~ + PartialInverse~A~ <|-- Inverse~A~ class Associative~A~{ Either[E, A: Associative] F[A: Associative]: Derive[_, Associative] @@ -118,7 +119,6 @@ classDiagram Min[Boolean] Min[Byte/Char/Double/Float/Int/Long/Short] Option[A: Associative] - Prod[Byte/Char/Double/Float/Int/Long/Short] String ❨T1: Identity, ..., T22: Identity❩ Validation[E, A: Identity] @@ -127,6 +127,12 @@ classDiagram () identity: A } + class PartialInverse~A~{ + F[A: PartialInverse]: Derive[_, PartialInverse] + Prod[Byte/Char/Double/Float/Int/Long/Short] + ❨T1: PartialInverse, ..., T22: PartialInverse❩ + () inverseOption(=> A, => A): Option[A] + } class Inverse~A~{ And F[A: Inverse]: Derive[_, Inverse] @@ -299,6 +305,27 @@ classDiagram ``` +# DistributiveProd + +```mermaid +classDiagram + DistributiveProd~A~ <|-- Annihilation~A~ + class DistributiveProd~A~{ + Cause[A] + fx.Cause[A] + ParSeq[Unit, A] + + () Sum: Associative[Sum[A]] + () Prod: Associative[Prod[A]] + () sum(=> A, => A): A + () prod(=> A, => A): A + } + class Annihilation~A~{ + Byte/Char/Double/Float/Int/Long/Short + } +``` + + ## Equal ```mermaid diff --git a/experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/AnnihilationLaws.scala b/experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/AnnihilationLaws.scala new file mode 100644 index 000000000..37276bc60 --- /dev/null +++ b/experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/AnnihilationLaws.scala @@ -0,0 +1,45 @@ +package zio.prelude.experimental.laws + +import zio.prelude.experimental._ +import zio.prelude.experimental.coherent._ +import zio.prelude.laws._ +import zio.test._ +import zio.test.laws._ + +object AnnihilationLaws extends Lawful[AnnihilationEqual] { + + /** + * The left annihilation law states that for the multiplication operator `*`, + * 0 (the identity value for addition) and for any value `a`, the following must hold: + * + * {{{ + * 0 * a === 0 + * }}} + */ + lazy val leftAnnihilationLaw: Laws[AnnihilationEqual] = + new Laws.Law1[AnnihilationEqual]("leftAnnihilationLaw") { + def apply[A](a: A)(implicit A: AnnihilationEqual[A]): TestResult = + (A.annihilation *** a) <-> A.annihilation + } + + /** + * The right annihilation law states that for the multiplication operator `*`, + * 0 (the identity value for addition) and for any value `a`, the following must hold: + * + * {{{ + * a * 0 === 0 + * }}} + */ + lazy val rightAnnihilationLaw: Laws[AnnihilationEqual] = + new Laws.Law1[AnnihilationEqual]("rightAnnihilationLaw") { + def apply[A](a: A)(implicit A: AnnihilationEqual[A]): TestResult = + (a *** A.annihilation) <-> A.annihilation + } + + /** + * The set of all laws that instances of `Annihilation` must satisfy. + */ + lazy val laws: Laws[AnnihilationEqual] = + DistributiveProdLaws.laws + leftAnnihilationLaw + rightAnnihilationLaw + +} diff --git a/experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/DistributiveProdLaws.scala b/experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/DistributiveProdLaws.scala new file mode 100644 index 000000000..545c17c73 --- /dev/null +++ b/experimental-laws/shared/src/main/scala/zio/prelude/experimental/laws/DistributiveProdLaws.scala @@ -0,0 +1,45 @@ +package zio.prelude.experimental.laws + +import zio.prelude.experimental._ +import zio.prelude.experimental.coherent._ +import zio.prelude.laws._ +import zio.test._ +import zio.test.laws._ + +object DistributiveProdLaws extends Lawful[DistributiveProdEqual] { + + /** + * The left distributivity law states that for operators `+` and `*`, for all + * values `a1`, `a2`, and `a3`, the following must hold: + * + * {{{ + * a1 * (a2 + a3) === (a1 * a2) + (a1 * a3) + * }}} + */ + lazy val leftDistributivityLaw: Laws[DistributiveProdEqual] = + new Laws.Law3[DistributiveProdEqual]("leftDistributivityLaw") { + def apply[A: DistributiveProdEqual](a1: A, a2: A, a3: A): TestResult = + (a1 *** (a2 +++ a3)) <-> ((a1 *** a2) +++ (a1 *** a3)) + } + + /** + * The right distributivity law states that for operators `+` and `*`, for all + * values `a1`, `a2`, and `a3`, the following must hold: + * + * {{{ + * (a1 + a2) * a3 === (a1 * a3) + (a2 * a3) + * }}} + */ + lazy val rightDistributivityLaw: Laws[DistributiveProdEqual] = + new Laws.Law3[DistributiveProdEqual]("rightDistributivityLaw") { + def apply[A: DistributiveProdEqual](a1: A, a2: A, a3: A): TestResult = + ((a1 +++ a2) *** a3) <-> ((a1 *** a3) +++ (a2 *** a3)) + } + + /** + * The set of all laws that instances of `DistributiveProd` must satisfy. + */ + lazy val laws: Laws[DistributiveProdEqual] = + leftDistributivityLaw + rightDistributivityLaw + +} diff --git a/experimental-tests/shared/src/test/scala/zio/prelude/experimental/AnnihilationSpec.scala b/experimental-tests/shared/src/test/scala/zio/prelude/experimental/AnnihilationSpec.scala new file mode 100644 index 000000000..5b19f6887 --- /dev/null +++ b/experimental-tests/shared/src/test/scala/zio/prelude/experimental/AnnihilationSpec.scala @@ -0,0 +1,29 @@ +package zio.prelude +package experimental + +import zio.prelude._ +import zio.prelude.experimental.laws._ +import zio.test._ +import zio.test.laws._ + +object AnnihilationSpec extends ZIOBaseSpec { + + private implicit val DoubleEqual: Equal[Double] = Equal.DoubleEqualWithEpsilon() + private implicit val FloatEqual: Equal[Float] = Equal.FloatEqualWithEpsilon() + + def spec: Spec[Environment, Any] = + suite("AnnihilationSpec")( + suite("laws")( + test("BigDecimal annihilating")( + checkAllLaws(AnnihilationLaws)(Gen.bigDecimal(BigDecimal(-10), BigDecimal(10))) + ), + test("byte annihilating")(checkAllLaws(AnnihilationLaws)(Gen.byte)), + test("char annihilating")(checkAllLaws(AnnihilationLaws)(Gen.char)), + test("double annihilating")(checkAllLaws(AnnihilationLaws)(Gen.double)), + test("float annihilating")(checkAllLaws(AnnihilationLaws)(Gen.float)), + test("int annihilating")(checkAllLaws(AnnihilationLaws)(Gen.int)), + test("long annihilating")(checkAllLaws(AnnihilationLaws)(Gen.long)), + test("short annihilating")(checkAllLaws(AnnihilationLaws)(Gen.short)) + ) + ) +} diff --git a/experimental-tests/shared/src/test/scala/zio/prelude/experimental/DistributiveProdSpec.scala b/experimental-tests/shared/src/test/scala/zio/prelude/experimental/DistributiveProdSpec.scala new file mode 100644 index 000000000..382243cfc --- /dev/null +++ b/experimental-tests/shared/src/test/scala/zio/prelude/experimental/DistributiveProdSpec.scala @@ -0,0 +1,19 @@ +package zio.prelude +package experimental + +import zio.prelude.experimental.laws._ +import zio.prelude.laws._ +import zio.test._ +import zio.test.laws._ + +object DistributiveProdSpec extends ZIOBaseSpec { + + def spec: Spec[Environment, Any] = + suite("DistributiveProdSpec")( + suite("laws")( + test("Cause distributive multiply")(checkAllLaws(DistributiveProdLaws)(Gen.causes(Gen.int, Gen.throwable))), + test("ParSeq distributive multiply")(checkAllLaws(DistributiveProdLaws)(Gens.parSeq(Gen.unit, Gen.int))), + test("fx.Cause distributive multiply")(checkAllLaws(DistributiveProdLaws)(Gens.parSeq(Gen.empty, Gen.int))) + ) + ) +} diff --git a/experimental-tests/shared/src/test/scala/zio/prelude/experimental/ZIOBaseSpec.scala b/experimental-tests/shared/src/test/scala/zio/prelude/experimental/ZIOBaseSpec.scala index ef597093c..fa405bf9e 100644 --- a/experimental-tests/shared/src/test/scala/zio/prelude/experimental/ZIOBaseSpec.scala +++ b/experimental-tests/shared/src/test/scala/zio/prelude/experimental/ZIOBaseSpec.scala @@ -6,5 +6,5 @@ import zio.test._ trait ZIOBaseSpec extends ZIOSpecDefault { override def aspects: Chunk[TestAspectAtLeastR[Environment with TestEnvironment]] = if (TestPlatform.isJVM) Chunk(TestAspect.timeout(60.seconds), TestAspect.timed) - else Chunk(TestAspect.timeout(60.seconds), TestAspect.sequential, TestAspect.timed) + else Chunk(TestAspect.timeout(300.seconds), TestAspect.sequential, TestAspect.timed) } diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/Annihilation.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/Annihilation.scala new file mode 100644 index 000000000..52e4aaa19 --- /dev/null +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/Annihilation.scala @@ -0,0 +1,18 @@ +package zio.prelude +package experimental + +import zio.prelude.newtypes.Sum + +trait Annihilation[A] extends DistributiveProd[A] { + def Sum: Identity[Sum[A]] + def annihilation: A = Sum.identity +} + +object Annihilation { + + /** + * Summons an implicit `Annihilation[A]`. + */ + def apply[A](implicit annihilation: Annihilation[A]): Annihilation[A] = annihilation + +} diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/DistributiveProd.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/DistributiveProd.scala new file mode 100644 index 000000000..575c00f38 --- /dev/null +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/DistributiveProd.scala @@ -0,0 +1,166 @@ +package zio.prelude +package experimental + +import zio.prelude.newtypes.{Prod, Sum} + +trait DistributiveProd[A] { + def Sum: Associative[Sum[A]] + def Prod: Associative[Prod[A]] + def sum(l: => A, r: => A): A = Sum.combine(newtypes.Sum(l), newtypes.Sum(r)) + def prod(l: => A, r: => A): A = Prod.combine(newtypes.Prod(l), newtypes.Prod(r)) +} + +object DistributiveProd extends DistributiveProdLowPriorityImplicits { + + /** + * Summons an implicit `DistributiveProd[A]`. + */ + def apply[A](implicit distributiveProd: DistributiveProd[A]): DistributiveProd[A] = distributiveProd + + implicit lazy val BigDecimalAnnihilationPartialDivideSubtract + : Annihilation[BigDecimal] with PartialDivide[BigDecimal] with Subtract[BigDecimal] = + new Annihilation[BigDecimal] with PartialDivide[BigDecimal] with Subtract[BigDecimal] { + override def sum(l: => BigDecimal, r: => BigDecimal): BigDecimal = l + r + override def divideOption(l: => BigDecimal, r: => BigDecimal): Option[BigDecimal] = + if (r != BigDecimal(0)) Some(l / r) else None + override def prod(l: => BigDecimal, r: => BigDecimal): BigDecimal = l * r + override def subtract(l: => BigDecimal, r: => BigDecimal): BigDecimal = l - r + override def annihilation: BigDecimal = 0.0 + val Sum: Commutative[Sum[BigDecimal]] with Inverse[Sum[BigDecimal]] = Associative.BigDecimalSumCommutativeInverse + val Prod: Commutative[Prod[BigDecimal]] with PartialInverse[Prod[BigDecimal]] = + Associative.BigDecimalProdCommutativePartialInverse + } + + implicit lazy val ByteAnnihilationSubtract: Annihilation[Byte] with Subtract[Byte] = + new Annihilation[Byte] with Subtract[Byte] { + override def sum(l: => Byte, r: => Byte): Byte = (l + r).toByte + override def prod(l: => Byte, r: => Byte): Byte = (l * r).toByte + override def subtract(l: => Byte, r: => Byte): Byte = (l - r).toByte + override def annihilation: Byte = 0 + val Sum: Commutative[Sum[Byte]] with Inverse[Sum[Byte]] = Associative.ByteSumCommutativeInverse + val Prod: Commutative[Prod[Byte]] with PartialInverse[Prod[Byte]] = Associative.ByteProdCommutativePartialInverse + } + + implicit lazy val CharAnnihilationSubtract: Annihilation[Char] with Subtract[Char] = + new Annihilation[Char] with Subtract[Char] { + override def sum(l: => Char, r: => Char): Char = (l + r).toChar + override def prod(l: => Char, r: => Char): Char = (l * r).toChar + override def subtract(l: => Char, r: => Char): Char = (l - r).toChar + override def annihilation: Char = 0 + val Sum: Commutative[Sum[Char]] with Inverse[Sum[Char]] = Associative.CharSumCommutativeInverse + val Prod: Commutative[Prod[Char]] with PartialInverse[Prod[Char]] = Associative.CharProdCommutativePartialInverse + } + + implicit lazy val DoubleAnnihilationPartialDivideSubtract + : Annihilation[Double] with PartialDivide[Double] with Subtract[Double] = + new Annihilation[Double] with PartialDivide[Double] with Subtract[Double] { + override def sum(l: => Double, r: => Double): Double = l + r + override def divideOption(l: => Double, r: => Double): Option[Double] = if (r != 0) Some(l / r) else None + override def prod(l: => Double, r: => Double): Double = l * r + override def subtract(l: => Double, r: => Double): Double = l - r + override def annihilation: Double = 0.0 + val Sum: Commutative[Sum[Double]] with Inverse[Sum[Double]] = Associative.DoubleSumCommutativeInverse + val Prod: Commutative[Prod[Double]] with PartialInverse[Prod[Double]] = + Associative.DoubleProdCommutativePartialInverse + } + + implicit lazy val FloatAnnihilationPartialDivideSubtract + : Annihilation[Float] with PartialDivide[Float] with Subtract[Float] = + new Annihilation[Float] with PartialDivide[Float] with Subtract[Float] { + override def sum(l: => Float, r: => Float): Float = l + r + override def divideOption(l: => Float, r: => Float): Option[Float] = if (r != 0) Some(l / r) else None + override def prod(l: => Float, r: => Float): Float = l * r + override def subtract(l: => Float, r: => Float): Float = l - r + override def annihilation: Float = 0.0f + val Sum: Commutative[Sum[Float]] with Inverse[Sum[Float]] = Associative.FloatSumCommutativeInverse + val Prod: Commutative[Prod[Float]] with PartialInverse[Prod[Float]] = + Associative.FloatProdCommutativePartialInverse + } + + implicit lazy val IntAnnihilationSubtract: Annihilation[Int] with Subtract[Int] = + new Annihilation[Int] with Subtract[Int] { + override def sum(l: => Int, r: => Int): Int = l + r + override def prod(l: => Int, r: => Int): Int = l * r + override def subtract(l: => Int, r: => Int): Int = l - r + override def annihilation: Int = 0 + val Sum: Commutative[Sum[Int]] with Inverse[Sum[Int]] = Associative.IntSumCommutativeInverse + val Prod: Commutative[Prod[Int]] with PartialInverse[Prod[Int]] = Associative.IntProdCommutativePartialInverse + } + + implicit lazy val LongAnnihilationSubtract: Annihilation[Long] with Subtract[Long] = + new Annihilation[Long] with Subtract[Long] { + override def sum(l: => Long, r: => Long): Long = l + r + override def prod(l: => Long, r: => Long): Long = l * r + override def subtract(l: => Long, r: => Long): Long = l - r + override def annihilation: Long = 0 + val Sum: Commutative[Sum[Long]] with Inverse[Sum[Long]] = Associative.LongSumCommutativeInverse + val Prod: Commutative[Prod[Long]] with PartialInverse[Prod[Long]] = Associative.LongProdCommutativePartialInverse + } + + implicit def ParSeqDistributiveProd[A]: DistributiveProd[ParSeq[Unit, A]] = + new DistributiveProd[ParSeq[Unit, A]] { + val Sum: Associative[Sum[ParSeq[Unit, A]]] = Associative.ParSeqSumCommutativeIdentity + val Prod: Associative[Prod[ParSeq[Unit, A]]] = Associative.ParSeqProdIdentity + } + + implicit lazy val ShortAnnihilationSubtract: Annihilation[Short] with Subtract[Short] = + new Annihilation[Short] with Subtract[Short] { + override def sum(l: => Short, r: => Short): Short = (l + r).toShort + override def prod(l: => Short, r: => Short): Short = (l * r).toShort + override def subtract(l: => Short, r: => Short): Short = (l - r).toShort + override def annihilation: Short = 0 + val Sum: Commutative[Sum[Short]] with Inverse[Sum[Short]] = Associative.ShortSumCommutativeInverse + val Prod: Commutative[Prod[Short]] with PartialInverse[Prod[Short]] = + Associative.ShortProdCommutativePartialInverse + } + + implicit def ZioCauseDistributiveProd[A]: DistributiveProd[zio.Cause[A]] = + new DistributiveProd[zio.Cause[A]] { + val Sum: Associative[Sum[zio.Cause[A]]] = Associative.zioCauseSumCommutativeIdentity + val Prod: Associative[Prod[zio.Cause[A]]] = Associative.zioCauseProdIdentity + } + +} + +trait DistributiveProdLowPriorityImplicits { + + implicit def FxCauseDistributiveProd[A]: DistributiveProd[fx.Cause[A]] = + new DistributiveProd[fx.Cause[A]] { + val Sum: Associative[Sum[fx.Cause[A]]] = Associative.FxCauseSumCommutative + val Prod: Associative[Prod[fx.Cause[A]]] = Associative.FxCauseProdAssociative + } + +} + +trait DistributiveProdSyntax { + + /** + * Provides infix syntax for adding or multiplying two values. + */ + implicit class DistributiveProdOps[A](private val l: A) { + + /** + * A symbolic alias for `sum`. + */ + def +++(r: => A)(implicit distributiveProd: DistributiveProd[A]): A = + distributiveProd.sum(l, r) + + /** + * Add two values. + */ + def sum(r: => A)(implicit distributiveProd: DistributiveProd[A]): A = + distributiveProd.sum(l, r) + + /** + * A symbolic alias for `prod`. + */ + def ***(r: => A)(implicit distributiveProd: DistributiveProd[A]): A = + distributiveProd.prod(l, r) + + /** + * Multiply two values. + */ + def prod(r: => A)(implicit distributiveProd: DistributiveProd[A]): A = + distributiveProd.prod(l, r) + } +} diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/Divide.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/Divide.scala new file mode 100644 index 000000000..57ba97c10 --- /dev/null +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/Divide.scala @@ -0,0 +1,30 @@ +package zio.prelude +package experimental + +import zio.prelude.newtypes.Prod + +trait Divide[A] extends PartialDivide[A] { + def Prod: Inverse[Prod[A]] + def divide(l: => A, r: => A): A = Prod.inverse(newtypes.Prod(l), newtypes.Prod(r)) +} + +trait DivideSyntax { + + /** + * Provides infix syntax for dividing two values. + */ + implicit class DivideOps[A](private val l: A) { + + /** + * A symbolic alias for `divide`. + */ + def -:-(r: => A)(implicit divide: Divide[A]): A = + divide.divide(l, r) + + /** + * Divides `l` by `r`. + */ + def divide(r: => A)(implicit divide: Divide[A]): A = + divide.divide(l, r) + } +} diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/PartialDivide.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/PartialDivide.scala new file mode 100644 index 000000000..95ac3238e --- /dev/null +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/PartialDivide.scala @@ -0,0 +1,30 @@ +package zio.prelude +package experimental + +import zio.prelude.newtypes.Prod + +trait PartialDivide[A] extends DistributiveProd[A] { + def Prod: PartialInverse[Prod[A]] + def divideOption(l: => A, r: => A): Option[A] = Prod.inverseOption(newtypes.Prod(l), newtypes.Prod(r)) +} + +trait PartialDivideSyntax { + + /** + * Provides infix syntax for dividing two values, with possible failure. + */ + implicit class PartialDivideOps[A](private val l: A) { + + /** + * A symbolic alias for `divideOption`. + */ + def -/-(r: => A)(implicit partialDivide: PartialDivide[A]): Option[A] = + partialDivide.divideOption(l, r) + + /** + * Divides `l` by `r`, possibly failing. + */ + def divideOption(r: => A)(implicit partialDivide: PartialDivide[A]): Option[A] = + partialDivide.divideOption(l, r) + } +} diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/Subtract.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/Subtract.scala new file mode 100644 index 000000000..3d8356e87 --- /dev/null +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/Subtract.scala @@ -0,0 +1,29 @@ +package zio.prelude +package experimental +import zio.prelude.newtypes.Sum + +trait Subtract[A] extends DistributiveProd[A] { + def Sum: Inverse[Sum[A]] + def subtract(l: => A, r: => A): A = Sum.inverse(newtypes.Sum(l), newtypes.Sum(r)) +} + +trait SubtractSyntax { + + /** + * Provides infix syntax for subtracting two values. + */ + implicit class SubtractOps[A](private val l: A) { + + /** + * A symbolic alias for `subtract`. + */ + def ---(r: => A)(implicit subtract: Subtract[A]): A = + subtract.subtract(l, r) + + /** + * Subtract two values. + */ + def subtract(r: => A)(implicit subtract: Subtract[A]): A = + subtract.subtract(l, r) + } +} diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/coherent/coherent.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/coherent/coherent.scala index 6a037c367..f3f401988 100644 --- a/experimental/shared/src/main/scala/zio/prelude/experimental/coherent/coherent.scala +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/coherent/coherent.scala @@ -2,7 +2,7 @@ package zio.prelude.experimental.coherent import zio.prelude._ import zio.prelude.experimental._ -import zio.prelude.newtypes.{AndF, OrF} +import zio.prelude.newtypes.{AndF, OrF, Prod, Sum} trait AbsorptionEqual[A] extends Absorption[A] with Equal[A] @@ -22,6 +22,24 @@ object AbsorptionEqual { } } +trait AnnihilationEqual[A] extends Annihilation[A] with DistributiveProdEqual[A] + +object AnnihilationEqual { + implicit def derive[A](implicit annihilation0: Annihilation[A], equal0: Equal[A]): AnnihilationEqual[A] = + new AnnihilationEqual[A] { + + override def sum(l: => A, r: => A): A = annihilation0.sum(l, r) + + override def prod(l: => A, r: => A): A = annihilation0.prod(l, r) + + override def Sum: Identity[Sum[A]] = annihilation0.Sum + + override def Prod: Associative[Prod[A]] = annihilation0.Prod + + protected def checkEqual(l: A, r: A): Boolean = equal0.equal(l, r) + } +} + trait DistributiveAbsorptionEqual[A] extends AbsorptionEqual[A] with DistributiveAbsorption[A] object DistributiveAbsorptionEqual { @@ -106,3 +124,21 @@ object NoncontradictionEqual { protected def checkEqual(l: A, r: A): Boolean = equal0.equal(l, r) } } + +trait DistributiveProdEqual[A] extends DistributiveProd[A] with Equal[A] + +object DistributiveProdEqual { + implicit def derive[A](implicit distributiveProd0: DistributiveProd[A], equal0: Equal[A]): DistributiveProdEqual[A] = + new DistributiveProdEqual[A] { + + override def sum(l: => A, r: => A): A = distributiveProd0.sum(l, r) + + override def prod(l: => A, r: => A): A = distributiveProd0.prod(l, r) + + override def Sum: Associative[Sum[A]] = distributiveProd0.Sum + + override def Prod: Associative[Prod[A]] = distributiveProd0.Prod + + protected def checkEqual(l: A, r: A): Boolean = equal0.equal(l, r) + } +} diff --git a/experimental/shared/src/main/scala/zio/prelude/experimental/package.scala b/experimental/shared/src/main/scala/zio/prelude/experimental/package.scala index 77b7e0d2d..47a00ba39 100644 --- a/experimental/shared/src/main/scala/zio/prelude/experimental/package.scala +++ b/experimental/shared/src/main/scala/zio/prelude/experimental/package.scala @@ -21,7 +21,11 @@ package object experimental with ApplicationComposeSyntax with BothComposeSyntax with ComplementSyntax - with EitherComposeSyntax { + with DistributiveProdSyntax + with DivideSyntax + with EitherComposeSyntax + with PartialDivideSyntax + with SubtractSyntax { object classic { diff --git a/laws/shared/src/main/scala/zio/prelude/laws/InverseLaws.scala b/laws/shared/src/main/scala/zio/prelude/laws/InverseLaws.scala index 80f571453..e58f87733 100644 --- a/laws/shared/src/main/scala/zio/prelude/laws/InverseLaws.scala +++ b/laws/shared/src/main/scala/zio/prelude/laws/InverseLaws.scala @@ -40,5 +40,5 @@ object InverseLaws extends Lawful[EqualInverse] { * The set of all laws that instances of `Inverse` must satisfy. */ lazy val laws: Laws[EqualInverse] = - inverseLaw + IdentityLaws.laws + PartialInverseLaws.laws + inverseLaw } diff --git a/laws/shared/src/main/scala/zio/prelude/laws/PartialInverseLaws.scala b/laws/shared/src/main/scala/zio/prelude/laws/PartialInverseLaws.scala new file mode 100644 index 000000000..bf9937bea --- /dev/null +++ b/laws/shared/src/main/scala/zio/prelude/laws/PartialInverseLaws.scala @@ -0,0 +1,32 @@ +package zio.prelude.laws + +import zio.prelude.coherent.EqualPartialInverse +import zio.test.laws._ +import zio.test.{TestResult, assertCompletes} + +object PartialInverseLaws extends Lawful[EqualPartialInverse] { + + /** + * The partial inverse law states that for some binary operator `*`, + * for all values `a`, if the operation is defined, the following must hold: + * + * {{{ + * a * a === identity + * }}} + */ + lazy val partialInverseLaw: Laws[EqualPartialInverse] = + new Laws.Law1[EqualPartialInverse]("rightPartialInverseLaw") { + def apply[A](a: A)(implicit I: EqualPartialInverse[A]): TestResult = + I.inverseOption(a, a) match { + case Some(a) => a <-> I.identity + case None => assertCompletes + } + } + + /** + * The set of all laws that instances of `PartialInverse` must satisfy. + */ + lazy val laws: Laws[EqualPartialInverse] = + IdentityLaws.laws + partialInverseLaw + +}