Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Polish Cats semigroups/monoids for refined types #277

Merged
merged 6 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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