Skip to content

Commit

Permalink
Merge pull request #4032 from tmccarthy/fractional-invariant
Browse files Browse the repository at this point in the history
Invariant[Fractional]
  • Loading branch information
LukaJCB authored Nov 4, 2021
2 parents 8787d59 + c10a45b commit 1a7b9d8
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 2 deletions.
2 changes: 2 additions & 0 deletions core/src/main/scala-2.12/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ trait AllInstancesBinCompat6 extends SortedSetInstancesBinCompat1 with SortedMap
trait AllInstancesBinCompat7 extends SeqInstances

trait AllInstancesBinCompat8 extends DeadlineInstances

trait AllInstancesBinCompat9 extends InvariantInstances with InvariantInstancesBinCompat0
2 changes: 1 addition & 1 deletion core/src/main/scala-2.12/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package object instances {
object partialFunction extends PartialFunctionInstances
object future extends FutureInstances
object int extends IntInstances
object invariant extends InvariantMonoidalInstances with InvariantInstances
object invariant extends InvariantMonoidalInstances with InvariantInstances with InvariantInstancesBinCompat0
object list extends ListInstances with ListInstancesBinCompat0
object long extends LongInstances
object option extends OptionInstances with OptionInstancesBinCompat0
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala-2.13+/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ trait AllInstancesBinCompat7 extends SeqInstances
trait AllInstancesBinCompat8 extends InvariantInstances

trait AllInstancesBinCompat9 extends DeadlineInstances

trait AllInstancesBinCompat10 extends InvariantInstancesBinCompat0
2 changes: 1 addition & 1 deletion core/src/main/scala-2.13+/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package object instances {
object partialFunction extends PartialFunctionInstances
object future extends FutureInstances
object int extends IntInstances
object invariant extends InvariantMonoidalInstances with InvariantInstances
object invariant extends InvariantMonoidalInstances with InvariantInstances with InvariantInstancesBinCompat0
object list extends ListInstances with ListInstancesBinCompat0
object long extends LongInstances
object option extends OptionInstances with OptionInstancesBinCompat0
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/Invariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ object Invariant extends ScalaVersionSpecificInvariantInstances with InvariantIn
cats.instances.invariant.catsInvariantForNumeric
implicit def catsInvariantForIntegral: Invariant[Integral] =
cats.instances.invariant.catsInvariantForIntegral
implicit def catsInvariantForFractional: Invariant[Fractional] =
cats.instances.invariant.catsInvariantForFractional

implicit val catsInvariantMonoid: Invariant[Monoid] = new Invariant[Monoid] {

Expand Down
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/instances/invariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,12 @@ trait InvariantInstances {
}
}
}

trait InvariantInstancesBinCompat0 {
implicit val catsInvariantForFractional: Invariant[Fractional] = new Invariant[Fractional] {
def imap[A, B](fa: Fractional[A])(f: A => B)(g: B => A): Fractional[B] =
new ScalaVersionSpecificNumeric[A, B](fa)(f)(g) with Fractional[B] {
override def div(x: B, y: B): B = f(fa.div(g(x), g(y)))
}
}
}
24 changes: 24 additions & 0 deletions tests/src/test/scala-2.12/cats/tests/ScalaVersionSpecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package cats.tests
import cats.laws.discipline.{ExhaustiveCheck, MiniInt}
import cats.laws.discipline.MiniInt._
import cats.laws.discipline.eq._
import cats.laws.discipline.DeprecatedEqInstances
import cats.kernel.{Eq, Order}
import org.scalacheck.Arbitrary

trait ScalaVersionSpecificFoldableSuite
trait ScalaVersionSpecificParallelSuite
trait ScalaVersionSpecificRegressionSuite
trait ScalaVersionSpecificTraverseSuite

trait ScalaVersionSpecificAlgebraInvariantSuite {
// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
protected val integralForMiniInt: Integral[MiniInt] = new Integral[MiniInt] {
def compare(x: MiniInt, y: MiniInt): Int = Order[MiniInt].compare(x, y)
def plus(x: MiniInt, y: MiniInt): MiniInt = x + y
Expand All @@ -26,6 +29,7 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
def rem(x: MiniInt, y: MiniInt): MiniInt = MiniInt.unsafeFromInt(x.toInt % y.toInt)
}

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
implicit protected def eqNumeric[A: Eq: ExhaustiveCheck]: Eq[Numeric[A]] = Eq.by { numeric =>
// This allows us to catch the case where the fromInt overflows. We use the None to compare two Numeric instances,
// verifying that when fromInt throws for one, it throws for the other.
Expand All @@ -49,4 +53,24 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
numeric.toDouble _
)
}

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
implicit protected def eqFractional[A: Eq: Arbitrary]: Eq[Fractional[A]] = {
import DeprecatedEqInstances.catsLawsEqForFn1

Eq.by { fractional =>
(
fractional.compare _,
fractional.plus _,
fractional.minus _,
fractional.times _,
fractional.negate _,
fractional.fromInt _,
fractional.toInt _,
fractional.toLong _,
fractional.toFloat _,
fractional.toDouble _
)
}
}
}
32 changes: 32 additions & 0 deletions tests/src/test/scala-2.13+/cats/tests/ScalaVersionSpecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cats.{Eval, Foldable, Id, Now}
import cats.data.NonEmptyLazyList
import cats.laws.discipline.{ExhaustiveCheck, MiniInt, NonEmptyParallelTests, ParallelTests}
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.DeprecatedEqInstances
import cats.syntax.either._
import cats.syntax.foldable._
import cats.syntax.parallel._
Expand All @@ -12,6 +13,7 @@ import cats.syntax.eq._
import org.scalacheck.Prop._
import cats.kernel.{Eq, Order}
import cats.laws.discipline.eq._
import org.scalacheck.Arbitrary

trait ScalaVersionSpecificFoldableSuite { self: FoldableSuiteAdditional =>
test("Foldable[LazyList].foldM stack safety") {
Expand Down Expand Up @@ -166,6 +168,7 @@ trait ScalaVersionSpecificTraverseSuite { self: TraverseSuiteAdditional =>

trait ScalaVersionSpecificAlgebraInvariantSuite {

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
protected val integralForMiniInt: Integral[MiniInt] = new Integral[MiniInt] {
def compare(x: MiniInt, y: MiniInt): Int = Order[MiniInt].compare(x, y)
def plus(x: MiniInt, y: MiniInt): MiniInt = x + y
Expand All @@ -182,6 +185,7 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
def parseString(str: String): Option[MiniInt] = Integral[Int].parseString(str).flatMap(MiniInt.fromInt)
}

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
implicit protected def eqNumeric[A: Eq: ExhaustiveCheck]: Eq[Numeric[A]] = Eq.by { numeric =>
// This allows us to catch the case where the fromInt overflows. We use the None to compare two Numeric instances,
// verifying that when fromInt throws for one, it throws for the other.
Expand Down Expand Up @@ -212,6 +216,34 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
)
}

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
implicit protected def eqFractional[A: Eq: Arbitrary]: Eq[Fractional[A]] = {
// This deprecated instance is required since there is not `ExhaustiveCheck` for any types for which a `Fractional`
// can easily be defined
import DeprecatedEqInstances.catsLawsEqForFn1

Eq.by { fractional =>
val parseFloatStrings: Option[Double] => Option[A] = {
case Some(f) => fractional.parseString(f.toString)
case None => fractional.parseString("invalid") // Use this to test parsing of non-numeric strings
}

(
fractional.compare _,
fractional.plus _,
fractional.minus _,
fractional.times _,
fractional.negate _,
fractional.fromInt _,
fractional.toInt _,
fractional.toLong _,
fractional.toFloat _,
fractional.toDouble _,
parseFloatStrings
)
}
}

}

class TraverseLazyListSuite extends TraverseSuite[LazyList]("LazyList")
Expand Down
4 changes: 4 additions & 0 deletions tests/src/test/scala/cats/tests/AlgebraInvariantSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ class AlgebraInvariantSuite extends CatsSuite with ScalaVersionSpecificAlgebraIn

implicit private val arbNumericMiniInt: Arbitrary[Numeric[MiniInt]] = Arbitrary(Gen.const(integralForMiniInt))
implicit private val arbIntegralMiniInt: Arbitrary[Integral[MiniInt]] = Arbitrary(Gen.const(integralForMiniInt))
implicit private val arbFractionalFloat: Arbitrary[Fractional[Float]] = Arbitrary(
Gen.const(implicitly[Fractional[Float]])
)

implicit protected def eqIntegral[A: Eq: ExhaustiveCheck]: Eq[Integral[A]] = {
def makeDivisionOpSafe(unsafeF: (A, A) => A): (A, A) => Option[A] =
Expand Down Expand Up @@ -196,6 +199,7 @@ class AlgebraInvariantSuite extends CatsSuite with ScalaVersionSpecificAlgebraIn

checkAll("Invariant[Numeric]", InvariantTests[Numeric].invariant[MiniInt, Boolean, Boolean])
checkAll("Invariant[Integral]", InvariantTests[Integral].invariant[MiniInt, Boolean, Boolean])
checkAll("Invariant[Fractional]", InvariantTests[Fractional].invariant[Float, Boolean, Boolean])

{
val S: Semigroup[Int] = Semigroup[Int].imap(identity)(identity)
Expand Down

0 comments on commit 1a7b9d8

Please sign in to comment.