Skip to content

Commit

Permalink
feat: Polish Cats semigroups/monoids for refined types (#277)
Browse files Browse the repository at this point in the history
Closes #271 
Closes #270 
Closes #268
  • Loading branch information
Iltotore authored Oct 14, 2024
1 parent a077d1d commit f71de9c
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 88 deletions.
3 changes: 2 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ object cats extends SubModule {
def artifactName = "iron-cats"

def ivyDeps = Agg(
ivy"org.typelevel::cats-core::2.8.0"
ivy"org.typelevel::cats-core::2.8.0",
ivy"org.typelevel::algebra::2.8.0"
)

object test extends Tests {
Expand Down
110 changes: 110 additions & 0 deletions cats/src/io/github/iltotore/iron/Bounds.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.github.iltotore.iron

import io.github.iltotore.iron.constraint.numeric.Greater
import io.github.iltotore.iron.constraint.numeric.*
import scala.compiletime.constValue
import scala.math.Numeric.Implicits.infixNumericOps
import scala.math.Numeric.IntIsIntegral
import scala.math.Ordering.Implicits.infixOrderingOps

/**
* A way to shift out-of-bounds [[A]] values constrained with [[C]] constraint.
*
* @tparam A the base type
* @tparam C the constraint type
*/
trait Bounds[A, C]:

/**
* Shift value if out of bounds.
*
* @param value the value to eventually shift
* @return the passed value as is or shifted if necessary
*/
def shift(value: A): A :| C

object Bounds:

/**
* Bounds for interval [L, U].
*
* @return
*/
inline given closedBounds[A, L <: A, U <: A, C](using
C ==> Interval.Closed[L, U],
Interval.Closed[L, U] ==> C,
Numeric[A]
): Bounds[A, C] =
new:
override def shift(value: A): A :| C =
if value > constValue[U] then (value - constValue[U] + constValue[L] - summon[Numeric[A]].one).assume[C]
else if value < constValue[L] then ((constValue[U]: A) + value - constValue[L] + summon[Numeric[A]].one).assume[C]
else value.assume[C]

//Positive
given posIntBounds[C](using C ==> Positive, Positive ==> C): Bounds[Int, C] = value =>
if value <= 0 then (value + Int.MaxValue).assume[C]
else value.assume[C]

given posLongBounds[C](using C ==> Positive, Positive ==> C): Bounds[Long, C] = value =>
if value <= 0 then (value + Long.MaxValue).assume[Positive]
else value.assume[C]

given posFloatBounds[C](using C ==> Positive, Positive ==> C): Bounds[Float, C] = value =>
if value <= 0 then (value + Float.MaxValue).assume[C]
else value.assume[C]

given posDoubleBounds[C](using C ==> Positive, Positive ==> C): Bounds[Double, C] = value =>
if value <= 0 then (value + Double.MaxValue).assume[C]
else value.assume[C]

//Positive0
given pos0IntBounds[C](using C ==> Positive0, Positive0 ==> C): Bounds[Int, C] = value =>
if value < 0 then (value + Int.MaxValue + 1).assume[C]
else value.assume[C]

given pos0LongBounds[C](using C ==> Positive0, Positive0 ==> C): Bounds[Long, C] = value =>
if value < 0 then (value + Long.MaxValue + 1).assume[C]
else value.assume[C]

given pos0FloatBounds[C](using C ==> Positive0, Positive0 ==> C): Bounds[Float, C] = value =>
if value < 0 then (value + Float.MaxValue + 1).assume[C]
else value.assume[C]

given pos0DoubleBounds[C](using C ==> Positive0, Positive0 ==> C): Bounds[Double, C] = value =>
if value < 0 then (value + Double.MaxValue + 1).assume[C]
else value.assume[C]

//Negative
given negIntBounds[C](using C ==> Negative, Negative ==> C): Bounds[Int, C] = value =>
if value >= 0 then (value + Int.MinValue).assume[C]
else value.assume[C]

given negLongBounds[C](using C ==> Negative, Negative ==> C): Bounds[Long, C] = value =>
if value >= 0 then (value + Long.MinValue).assume[C]
else value.assume[C]

given negFloatBounds[C](using C ==> Negative, Negative ==> C): Bounds[Float, C] = value =>
if value >= 0 then (value + Float.MinValue).assume[C]
else value.assume[C]

given negDoubleBounds[C](using C ==> Negative, Negative ==> C): Bounds[Double, C] = value =>
if value >= 0 then (value + Double.MinValue).assume[C]
else value.assume[C]

//Negative
given neg0IntBounds[C](using C ==> Negative0, Negative0 ==> C): Bounds[Int, C] = value =>
if value > 0 then (value + Int.MinValue - 1).assume[C]
else value.assume[C]

given neg0LongBounds[C](using C ==> Negative0, Negative0 ==> C): Bounds[Long, C] = value =>
if value > 0 then (value + Long.MinValue - 1).assume[C]
else value.assume[C]

given neg0FloatBounds[C](using C ==> Negative0, Negative0 ==> C): Bounds[Float, C] = value =>
if value > 0 then (value + Float.MinValue - 1).assume[C]
else value.assume[C]

given neg0DoubleBounds[C](using C ==> Negative0, Negative0 ==> C): Bounds[Double, C] = value =>
if value > 0 then (value + Double.MinValue - 1).assume[C]
else value.assume[C]
79 changes: 2 additions & 77 deletions cats/src/io/github/iltotore/iron/cats.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package io.github.iltotore.iron

import _root_.cats.data.*
import _root_.cats.kernel.{CommutativeMonoid, Hash, LowerBounded, PartialOrder, UpperBounded}
import _root_.cats.data.{EitherNec, EitherNel, Validated, ValidatedNec, ValidatedNel}
import _root_.cats.syntax.either.*
import _root_.cats.{Eq, Monoid, Order, Show, Traverse}
import _root_.cats.data.Validated.{Invalid, Valid}
import _root_.cats.Functor
import _root_.cats.implicits.*
Expand All @@ -12,6 +10,7 @@ import io.github.iltotore.iron.constraint.numeric.{Greater, Less, Negative, Posi
import scala.util.NotGiven
import scala.util.boundary
import scala.util.boundary.break
import _root_.cats.Traverse

object cats extends IronCatsInstances:

Expand Down Expand Up @@ -322,77 +321,3 @@ object cats extends IronCatsInstances:
traverse.traverse(wrapper): value =>
Validated.condNel[InvalidValue[A], T](ops.rtc.test(value), ops.assume(value), InvalidValue(value, ops.rtc.message))

/**
* Represent all Cats' typeclass instances for Iron.
*/
private trait IronCatsInstances extends IronCatsLowPriority, RefinedTypeOpsCats:

given [F[_]](using functor: Functor[F]): MapLogic[F] with

override def map[A, B](wrapper: F[A], f: A => B): F[B] = functor.map(wrapper)(f)

// The `NotGiven` implicit parameter is mandatory to avoid ambiguous implicit error when both Eq[A] and Hash[A]/PartialOrder[A] exist
inline given [A, C](using inline ev: Eq[A], notHashOrOrder: NotGiven[Hash[A] | PartialOrder[A]]): Eq[A :| C] = ev.asInstanceOf[Eq[A :| C]]

inline given [A, C](using inline ev: PartialOrder[A], notOrder: NotGiven[Order[A]]): PartialOrder[A :| C] = ev.asInstanceOf[PartialOrder[A :| C]]

inline given [A, C](using inline ev: Order[A]): Order[A :| C] = ev.asInstanceOf[Order[A :| C]]

inline given [A, C](using inline ev: Show[A]): Show[A :| C] = ev.asInstanceOf[Show[A :| C]]

inline given [A, C, V](using inline ev: LowerBounded[A], implication: C ==> Greater[V]): LowerBounded[A :| C] =
ev.asInstanceOf[LowerBounded[A :| C]]

inline given [A, C, V](using inline ev: UpperBounded[A], implication: C ==> Greater[V]): UpperBounded[A :| C] =
ev.asInstanceOf[UpperBounded[A :| C]]

private def posMonoid[A, C](using ev: CommutativeMonoid[A], shift: PosShift[A], implication: C ==> Positive): CommutativeMonoid[A :| C] =
new CommutativeMonoid[A :| C]:

override def empty: A :| C = ev.empty.asInstanceOf[A :| C]

override def combine(a: A :| C, b: A :| C): A :| C = shift.shift(ev.combine(a, b)).asInstanceOf[A :| C]

inline given posIntCommutativeMonoid[C](using C ==> Positive): CommutativeMonoid[Int :| C] = posMonoid

inline given posLongCommutativeMonoid[C](using C ==> Positive): CommutativeMonoid[Long :| C] = posMonoid

inline given posFloatCommutativeMonoid[C](using C ==> Positive): CommutativeMonoid[Float :| C] = posMonoid

inline given posDoubleCommutativeMonoid[C](using C ==> Positive): CommutativeMonoid[Double :| C] = posMonoid

private def negMonoid[A, C](using ev: CommutativeMonoid[A], shift: NegShift[A], implication: C ==> Negative): CommutativeMonoid[A :| C] =
new CommutativeMonoid[A :| C]:

override def empty: A :| C = ev.empty.asInstanceOf[A :| C]

override def combine(a: A :| C, b: A :| C): A :| C = shift.shift(ev.combine(a, b)).asInstanceOf[A :| C]

inline given negIntCommutativeMonoid[C](using C ==> Negative): CommutativeMonoid[Int :| C] = negMonoid

inline given negLongCommutativeMonoid[C](using C ==> Negative): CommutativeMonoid[Long :| C] = negMonoid

inline given negFloatCommutativeMonoid[C](using C ==> Negative): CommutativeMonoid[Float :| C] = negMonoid

inline given negDoubleCommutativeMonoid[C](using C ==> Negative): CommutativeMonoid[Double :| C] = negMonoid

/**
* Cats' instances for Iron that need to have a lower priority to avoid ambiguous implicits.
*/
private trait IronCatsLowPriority:

inline given [A, C](using inline ev: Hash[A]): Hash[A :| C] = ev.asInstanceOf[Hash[A :| C]]

private trait RefinedTypeOpsCats extends RefinedTypeOpsCatsLowPriority:

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Eq[mirror.IronType]): Eq[T] = ev.asInstanceOf[Eq[T]]

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Order[mirror.IronType]): Order[T] = ev.asInstanceOf[Order[T]]

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Show[mirror.IronType]): Show[T] = ev.asInstanceOf[Show[T]]

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: PartialOrder[mirror.IronType]): PartialOrder[T] = ev.asInstanceOf[PartialOrder[T]]

private trait RefinedTypeOpsCatsLowPriority:

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]
120 changes: 120 additions & 0 deletions cats/src/io/github/iltotore/iron/instances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.github.iltotore.iron

import _root_.cats.kernel.{CommutativeMonoid, CommutativeSemigroup, Hash, LowerBounded, PartialOrder, UpperBounded}
import _root_.cats.{Eq, Monoid, Order, Show, Traverse}
import io.github.iltotore.iron.constraint.numeric.*
import scala.util.NotGiven
import _root_.cats.Functor
import algebra.instances.all.*
import algebra.ring.{AdditiveCommutativeMonoid, AdditiveCommutativeSemigroup, MultiplicativeGroup, MultiplicativeMonoid}

/**
* Represent all Cats' typeclass instances for Iron.
*/
private[iron] trait IronCatsInstances extends IronCatsLowPriority, RefinedTypeOpsCats:

given [F[_]](using functor: Functor[F]): MapLogic[F] with

override def map[A, B](wrapper: F[A], f: A => B): F[B] = functor.map(wrapper)(f)

// The `NotGiven` implicit parameter is mandatory to avoid ambiguous implicit error when both Eq[A] and Hash[A]/PartialOrder[A] exist
inline given [A, C](using inline ev: Eq[A], notHashOrOrder: NotGiven[Hash[A] | PartialOrder[A]]): Eq[A :| C] = ev.asInstanceOf[Eq[A :| C]]

inline given [A, C](using inline ev: PartialOrder[A], notOrder: NotGiven[Order[A]]): PartialOrder[A :| C] = ev.asInstanceOf[PartialOrder[A :| C]]

inline given [A, C](using inline ev: Order[A]): Order[A :| C] = ev.asInstanceOf[Order[A :| C]]

inline given [A, C](using inline ev: Show[A]): Show[A :| C] = ev.asInstanceOf[Show[A :| C]]

inline given [A, C, V](using inline ev: LowerBounded[A], implication: C ==> Greater[V]): LowerBounded[A :| C] =
ev.asInstanceOf[LowerBounded[A :| C]]

inline given [A, C, V](using inline ev: UpperBounded[A], implication: C ==> Greater[V]): UpperBounded[A :| C] =
ev.asInstanceOf[UpperBounded[A :| C]]

private def commutativeSemigroup[A, C](using inner: CommutativeSemigroup[A], bounds: Bounds[A, C]): CommutativeSemigroup[A :| C] =
new CommutativeSemigroup[A :| C]:

override def combine(a: A :| C, b: A :| C): A :| C = bounds.shift(inner.combine(a, b))

given posIntCommutativeSemigroup: CommutativeSemigroup[Int :| Positive] = commutativeSemigroup[Int, Positive]
given posLongCommutativeSemigroup: CommutativeSemigroup[Long :| Positive] = commutativeSemigroup[Long, Positive]
given posFloatCommutativeSemigroup: CommutativeSemigroup[Float :| Positive] = commutativeSemigroup[Float, Positive]
given posDoubleCommutativeSemigroup: CommutativeSemigroup[Double :| Positive] = commutativeSemigroup[Double, Positive]

given negIntCommutativeSemigroup: CommutativeSemigroup[Int :| Negative] = commutativeSemigroup[Int, Negative]
given negLongCommutativeSemigroup: CommutativeSemigroup[Long :| Negative] = commutativeSemigroup[Long, Negative]
given negFloatCommutativeSemigroup: CommutativeSemigroup[Float :| Negative] = commutativeSemigroup[Float, Negative]
given negDoubleCommutativeSemigroup: CommutativeSemigroup[Double :| Negative] = commutativeSemigroup[Double, Negative]

private def commutativeMonoid[A, C](using inner: CommutativeMonoid[A], bounds: Bounds[A, C]): CommutativeMonoid[A :| C] =
new CommutativeMonoid[A :| C]:

override def empty: A :| C = inner.empty.assume[C]

override def combine(a: A :| C, b: A :| C): A :| C = bounds.shift(inner.combine(a, b))

given posIntCommutativeMonoid: CommutativeMonoid[Int :| Positive0] = commutativeMonoid[Int, Positive0]
given posLongCommutativeMonoid: CommutativeMonoid[Long :| Positive0] = commutativeMonoid[Long, Positive0]
given posFloatCommutativeMonoid: CommutativeMonoid[Float :| Positive0] = commutativeMonoid[Float, Positive0]
given posDoubleCommutativeMonoid: CommutativeMonoid[Double :| Positive0] = commutativeMonoid[Double, Positive0]

given negIntCommutativeMonoid: CommutativeMonoid[Int :| Negative0] = commutativeMonoid[Int, Negative0]
given negLongCommutativeMonoid: CommutativeMonoid[Long :| Negative0] = commutativeMonoid[Long, Negative0]
given negFloatCommutativeMonoid: CommutativeMonoid[Float :| Negative0] = commutativeMonoid[Float, Negative0]
given negDoubleCommutativeMonoid: CommutativeMonoid[Double :| Negative0] = commutativeMonoid[Double, Negative0]

/**
* Cats' instances for Iron that need to have a lower priority to avoid ambiguous implicits.
*/
private trait IronCatsLowPriority:

inline given [A, C](using inline ev: Hash[A]): Hash[A :| C] = ev.asInstanceOf[Hash[A :| C]]

private trait RefinedTypeOpsCats extends RefinedTypeOpsCatsLowPriority:

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Eq[mirror.IronType]): Eq[T] = ev.asInstanceOf[Eq[T]]

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Order[mirror.IronType]): Order[T] = ev.asInstanceOf[Order[T]]

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Show[mirror.IronType]): Show[T] = ev.asInstanceOf[Show[T]]

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: PartialOrder[mirror.IronType]): PartialOrder[T] = ev.asInstanceOf[PartialOrder[T]]

private trait RefinedTypeOpsCatsLowPriority:

inline given [T](using mirror: RefinedTypeOps.Mirror[T], ev: Hash[mirror.IronType]): Hash[T] = ev.asInstanceOf[Hash[T]]

private def additiveCommutativeSemigroup[A, C](using inner: AdditiveCommutativeSemigroup[A], bounds: Bounds[A, C]): AdditiveCommutativeSemigroup[A :| C] = (x, y) =>
bounds.shift(inner.plus(x, y))

given posIntAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Int :| Positive] = additiveCommutativeSemigroup[Int, Positive]
given posLongAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Long :| Positive] = additiveCommutativeSemigroup[Long, Positive]
given posFloatAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Float :| Positive] = additiveCommutativeSemigroup[Float, Positive]
given posDoubleAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Double :| Positive] = additiveCommutativeSemigroup[Double, Positive]

given negIntAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Int :| Negative] = additiveCommutativeSemigroup[Int, Negative]
given negLongAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Long :| Negative] = additiveCommutativeSemigroup[Long, Negative]
given negFloatAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Float :| Negative] = additiveCommutativeSemigroup[Float, Negative]
given negDoubleAdditiveCommutativeSemigroup: AdditiveCommutativeSemigroup[Double :| Negative] = additiveCommutativeSemigroup[Double, Negative]

private def additiveCommutativeMonoid[A, C](using inner: AdditiveCommutativeMonoid[A], bounds: Bounds[A, C]): AdditiveCommutativeMonoid[A :| C] = new:

override def zero: A :| C = inner.zero.assume[C]
override def plus(x: A :| C, y: A :| C): A :| C = bounds.shift(inner.plus(x, y))

given posIntAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Int :| Positive0] = additiveCommutativeMonoid[Int, Positive0]
given posLongAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Long :| Positive0] = additiveCommutativeMonoid[Long, Positive0]
given posFloatAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Float :| Positive0] = additiveCommutativeMonoid[Float, Positive0]
given posDoubleAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Double :| Positive0] = additiveCommutativeMonoid[Double, Positive0]

given negIntAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Int :| Negative0] = additiveCommutativeMonoid[Int, Negative0]
given negLongAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Long :| Negative0] = additiveCommutativeMonoid[Long, Negative0]
given negFloatAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Float :| Negative0] = additiveCommutativeMonoid[Float, Negative0]
given negDoubleAdditiveCommutativeMonoid: AdditiveCommutativeMonoid[Double :| Negative0] = additiveCommutativeMonoid[Double, Negative0]

given multiplicativeMonoid[A, C](using inner: MultiplicativeMonoid[A]): MultiplicativeMonoid[A :| C] =
inner.assumeAll[C]

given multiplicativeGroup[A, C](using inner: MultiplicativeGroup[A]): MultiplicativeGroup[A :| C] =
inner.assumeAll[C]
Loading

0 comments on commit f71de9c

Please sign in to comment.