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

Invariant[Fractional] #4032

Merged
merged 3 commits into from
Nov 4, 2021
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
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we need to use this instance since there is no type for which both ExhaustiveCheck and Fractional are defined. I attempted to fix this in #3813 with a new MiniFloat type but that made the PR to big.


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