Skip to content

Commit

Permalink
Merge pull request #4216 from tmccarthy/tmccarthy/use-fractional-miniint
Browse files Browse the repository at this point in the history
Use spurious `Fractional[MiniInt]` to test `Invariant[Fractional]`
  • Loading branch information
armanbilge authored Jun 9, 2022
2 parents b351d67 + 75c8d09 commit 5f0a292
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import cats.kernel.{Eq, Order}
import cats.laws.discipline.{ExhaustiveCheck, MiniInt}
import cats.laws.discipline.MiniInt._
import cats.laws.discipline.eq._
import cats.laws.discipline.DeprecatedEqInstances
import org.scalacheck.Arbitrary

trait ScalaVersionSpecificFoldableSuite
trait ScalaVersionSpecificParallelSuite
Expand All @@ -35,7 +33,7 @@ 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] {
protected trait MiniIntNumeric extends Numeric[MiniInt] {
def compare(x: MiniInt, y: MiniInt): Int = Order[MiniInt].compare(x, y)
def plus(x: MiniInt, y: MiniInt): MiniInt = x + y
def minus(x: MiniInt, y: MiniInt): MiniInt = x + (-y)
Expand All @@ -46,8 +44,6 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
def toLong(x: MiniInt): Long = x.toInt.toLong
def toFloat(x: MiniInt): Float = x.toInt.toFloat
def toDouble(x: MiniInt): Double = x.toInt.toDouble
def quot(x: MiniInt, y: MiniInt): MiniInt = MiniInt.unsafeFromInt(x.toInt / y.toInt)
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
Expand Down Expand Up @@ -75,24 +71,4 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
)
}

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
@annotation.nowarn("cat=deprecation")
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 _
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ package cats.tests

import cats._
import cats.data.NonEmptyLazyList
import cats.laws.discipline.DeprecatedEqInstances
import cats.laws.discipline.ExhaustiveCheck
import cats.laws.discipline.MiniInt
import cats.laws.discipline.NonEmptyParallelTests
import cats.laws.discipline.ParallelTests
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
import cats.syntax.all._
import org.scalacheck.Arbitrary
import org.scalacheck.Prop._

trait ScalaVersionSpecificFoldableSuite { self: FoldableSuiteAdditional =>
Expand Down Expand Up @@ -191,7 +189,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] {
protected trait MiniIntNumeric extends Numeric[MiniInt] {
def compare(x: MiniInt, y: MiniInt): Int = Order[MiniInt].compare(x, y)
def plus(x: MiniInt, y: MiniInt): MiniInt = x + y
def minus(x: MiniInt, y: MiniInt): MiniInt = x + (-y)
Expand All @@ -202,8 +200,6 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
def toLong(x: MiniInt): Long = x.toInt.toLong
def toFloat(x: MiniInt): Float = x.toInt.toFloat
def toDouble(x: MiniInt): Double = x.toInt.toDouble
def quot(x: MiniInt, y: MiniInt): MiniInt = MiniInt.unsafeFromInt(x.toInt / y.toInt)
def rem(x: MiniInt, y: MiniInt): MiniInt = MiniInt.unsafeFromInt(x.toInt % y.toInt)
def parseString(str: String): Option[MiniInt] = Integral[Int].parseString(str).flatMap(MiniInt.fromInt)
}

Expand Down Expand Up @@ -238,35 +234,6 @@ trait ScalaVersionSpecificAlgebraInvariantSuite {
)
}

// This version-specific instance is required since 2.12 and below do not have parseString on the Numeric class
@annotation.nowarn("cat=deprecation")
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
40 changes: 33 additions & 7 deletions tests/shared/src/test/scala/cats/tests/AlgebraInvariantSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,14 @@ class AlgebraInvariantSuite extends CatsSuite with ScalaVersionSpecificAlgebraIn
implicit private val arbCommutativeGroupInt: Arbitrary[CommutativeGroup[Int]] =
Arbitrary(genCommutativeGroupInt)

protected val integralForMiniInt: Numeric[MiniInt] with Integral[MiniInt] = new MiniIntNumeric
with Integral[MiniInt] {
def quot(x: MiniInt, y: MiniInt): MiniInt = MiniInt.unsafeFromInt(x.toInt / y.toInt)
def rem(x: MiniInt, y: MiniInt): MiniInt = MiniInt.unsafeFromInt(x.toInt % y.toInt)
}

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 All @@ -209,6 +212,14 @@ class AlgebraInvariantSuite extends CatsSuite with ScalaVersionSpecificAlgebraIn
}
}

implicit protected def eqFractional[A: Eq: ExhaustiveCheck]: Eq[Fractional[A]] =
Eq.by { fractional =>
(
fractional: Numeric[A],
fractional.div(_, _)
)
}

checkAll("InvariantMonoidal[Semigroup]", SemigroupTests[Int](InvariantMonoidal[Semigroup].point(0)).semigroup)
checkAll("InvariantMonoidal[CommutativeSemigroup]",
CommutativeSemigroupTests[Int](InvariantMonoidal[CommutativeSemigroup].point(0)).commutativeSemigroup
Expand All @@ -218,10 +229,6 @@ class AlgebraInvariantSuite extends CatsSuite with ScalaVersionSpecificAlgebraIn
InvariantSemigroupalTests[Monoid].invariantSemigroupal[Option[MiniInt], Option[Boolean], Option[Boolean]]
)

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)
checkAll("Semigroup[Int]", SemigroupTests[Int](S).semigroup)
Expand Down Expand Up @@ -310,6 +317,25 @@ class AlgebraInvariantSuite extends CatsSuite with ScalaVersionSpecificAlgebraIn
checkAll("Invariant[CommutativeGroup]", InvariantTests[CommutativeGroup].invariant[MiniInt, Boolean, Boolean])
checkAll("Invariant[CommutativeGroup]", SerializableTests.serializable(Invariant[CommutativeGroup]))

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

{
// This is a spurious instance since MiniInt is not a Fractional data type. But we use it since we don't have a
// Fractional type for which ExhaustiveCheck is also implemented. See https://github.com/typelevel/cats/pull/4033
val fractionalForMiniInt: Fractional[MiniInt] = new MiniIntNumeric with Fractional[MiniInt] {
def div(x: MiniInt, y: MiniInt): MiniInt =
if (y == MiniInt.zero) {
MiniInt.maxValue
} else {
x / y
}
}
implicit val arbFractionalMiniInt: Arbitrary[Fractional[MiniInt]] = Arbitrary(Gen.const(fractionalForMiniInt))

checkAll("Invariant[Fractional]", InvariantTests[Fractional].invariant[MiniInt, Boolean, Boolean])
}

checkAll("InvariantMonoidal[Semigroup]",
InvariantMonoidalTests[Semigroup].invariantMonoidal[Option[MiniInt], Option[Boolean], Option[Boolean]]
)
Expand Down

0 comments on commit 5f0a292

Please sign in to comment.