Skip to content

Commit

Permalink
Bring Ring-like structures to ZIO Prelude (#351)
Browse files Browse the repository at this point in the history
  • Loading branch information
sideeffffect authored May 12, 2024
1 parent b465abb commit 6ada8f2
Show file tree
Hide file tree
Showing 23 changed files with 1,577 additions and 60 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import BuildHelper._

Global / onChangedBuildSource := ReloadOnSourceChanges
Global / concurrentRestrictions += Tags.limit(NativeTags.Link, 1)

inThisBuild(
List(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
Expand Down
Original file line number Diff line number Diff line change
@@ -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(_))))
)
)
}
148 changes: 104 additions & 44 deletions core/shared/src/main/scala/zio/prelude/Associative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

/**
Expand Down
15 changes: 9 additions & 6 deletions core/shared/src/main/scala/zio/prelude/Inverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
}
Loading

0 comments on commit 6ada8f2

Please sign in to comment.