diff --git a/free/src/test/scala/cats/free/FreeInvariantMonoidalSuite.scala b/free/src/test/scala/cats/free/FreeInvariantMonoidalSuite.scala index 1802b449bd..6420ea6533 100644 --- a/free/src/test/scala/cats/free/FreeInvariantMonoidalSuite.scala +++ b/free/src/test/scala/cats/free/FreeInvariantMonoidalSuite.scala @@ -3,10 +3,11 @@ package tests import cats.arrow.FunctionK import cats.free.FreeInvariantMonoidal -import cats.laws.discipline.{InvariantMonoidalTests, SerializableTests} +import cats.laws.discipline.{InvariantMonoidalTests, MiniInt, SerializableTests} +import cats.laws.discipline.arbitrary._ import cats.laws.discipline.SemigroupalTests.Isomorphisms import org.scalacheck.{Arbitrary, Gen} -import cats.tests.CsvCodecInvariantMonoidalSuite._ +import cats.tests.BinCodecInvariantMonoidalSuite._ class FreeInvariantMonoidalSuite extends CatsSuite { implicit def freeInvariantMonoidalArbitrary[F[_], A](implicit F: Arbitrary[F[A]], @@ -25,24 +26,25 @@ class FreeInvariantMonoidalSuite extends CatsSuite { } } - implicit val isoFreeCsvCodec = Isomorphisms.invariant[FreeInvariantMonoidal[CsvCodec, ?]] + implicit val isoFreeBinCodec = Isomorphisms.invariant[FreeInvariantMonoidal[BinCodec, ?]] - checkAll("FreeInvariantMonoidal[CsvCodec, ?]", - InvariantMonoidalTests[FreeInvariantMonoidal[CsvCodec, ?]].invariantMonoidal[Int, Int, Int]) - checkAll("InvariantMonoidal[FreeInvariantMonoidal[CsvCodec, ?]]", - SerializableTests.serializable(InvariantMonoidal[FreeInvariantMonoidal[CsvCodec, ?]])) + checkAll("FreeInvariantMonoidal[BinCodec, ?]", + InvariantMonoidalTests[FreeInvariantMonoidal[BinCodec, ?]].invariantMonoidal[MiniInt, Boolean, Boolean]) + checkAll("InvariantMonoidal[FreeInvariantMonoidal[BinCodec, ?]]", + SerializableTests.serializable(InvariantMonoidal[FreeInvariantMonoidal[BinCodec, ?]])) test("FreeInvariantMonoidal#fold") { - val n = 2 - val i1 = numericSystemCodec(8) - val i2 = InvariantMonoidal[CsvCodec].point(n) - val iExpr = i1.product(i2.imap(_ * 2)(_ / 2)) + forAll { i1: BinCodec[MiniInt] => + val n = MiniInt.unsafeFromInt(2) + val i2 = InvariantMonoidal[BinCodec].point(n) + val iExpr = i1.product(i2.imap(_ * n)(_ / n)) - val f1 = FreeInvariantMonoidal.lift[CsvCodec, Int](i1) - val f2 = FreeInvariantMonoidal.pure[CsvCodec, Int](n) - val fExpr = f1.product(f2.imap(_ * 2)(_ / 2)) + val f1 = FreeInvariantMonoidal.lift[BinCodec, MiniInt](i1) + val f2 = FreeInvariantMonoidal.pure[BinCodec, MiniInt](n) + val fExpr = f1.product(f2.imap(_ * n)(_ / n)) - fExpr.fold should ===(iExpr) + fExpr.fold should ===(iExpr) + } } implicit val idIsInvariantMonoidal: InvariantMonoidal[Id] = new InvariantMonoidal[Id] { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 5fbab9ead2..fa99582c5e 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -350,7 +350,7 @@ object arbitrary extends ArbitraryInstances0 { Cogen[List[A]].contramap(_.toList) implicit val catsLawsCogenForMiniInt: Cogen[MiniInt] = - Cogen[Int].contramap(_.asInt) + Cogen[Int].contramap(_.toInt) implicit val catsLawsArbitraryForMiniInt: Arbitrary[MiniInt] = Arbitrary(Gen.oneOf(MiniInt.allValues)) diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index 4a34d2b14c..c6bd200676 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -15,57 +15,65 @@ import cats.kernel._ import cats.syntax.eq._ import org.scalacheck.Arbitrary -object eq extends DisciplineEqInstances { +object eq extends DisciplineBinCompatEqInstances { implicit def catsLawsEqForFn1Exhaustive[A, B](implicit A: ExhaustiveCheck[A], B: Eq[B]): Eq[A => B] = Eq.instance((f, g) => A.allValues.forall(a => B.eqv(f(a), g(a)))) - implicit def catsLawsEqForFn2Exhaustive[A, B, C](implicit A: ExhaustiveCheck[A], - B: ExhaustiveCheck[B], - C: Eq[C]): Eq[(A, B) => C] = - Eq.by((_: (A, B) => C).tupled)(catsLawsEqForFn1Exhaustive) + implicit def catsLawsEqForFn2BinCompat[A, B, C](implicit ev: Eq[((A, B)) => C]): Eq[(A, B) => C] = + Eq.by((_: (A, B) => C).tupled) - implicit def catsLawsEqForAndThenExhaustive[A, B](implicit A: ExhaustiveCheck[A], B: Eq[B]): Eq[AndThen[A, B]] = - Eq.instance(catsLawsEqForFn1Exhaustive[A, B].eqv(_, _)) + implicit def catsLawsEqForAndThenBinCompat[A, B](implicit eqAB: Eq[A => B]): Eq[AndThen[A, B]] = + Eq.by[AndThen[A, B], A => B](identity) - implicit def catsLawsEqForShowExhaustive[A: ExhaustiveCheck]: Eq[Show[A]] = + implicit def catsLawsEqForShowBinCompat[A](implicit ev: Eq[A => String]): Eq[Show[A]] = Eq.by[Show[A], A => String](showA => a => showA.show(a)) - implicit def catsLawsEqForEqExhaustive[A: ExhaustiveCheck]: Eq[Eq[A]] = + implicit def catsLawsEqForEqBinCompt[A](implicit ev: Eq[(A, A) => Boolean]): Eq[Eq[A]] = Eq.by[Eq[A], (A, A) => Boolean](e => (a1, a2) => e.eqv(a1, a2)) - implicit def catsLawsEqForEquivExhaustive[A: ExhaustiveCheck]: Eq[Equiv[A]] = + implicit def catsLawsEqForEquivBinCompat[A](implicit ev: Eq[(A, A) => Boolean]): Eq[Equiv[A]] = Eq.by[Equiv[A], (A, A) => Boolean](e => (a1, a2) => e.equiv(a1, a2)) - implicit def catsLawsEqForPartialOrderExhaustive[A: ExhaustiveCheck]: Eq[PartialOrder[A]] = { - import cats.instances.option._ - + implicit def catsLawsEqForPartialOrderBinCompat[A](implicit ev: Eq[(A, A) => Option[Int]]): Eq[PartialOrder[A]] = Eq.by[PartialOrder[A], (A, A) => Option[Int]](o => (a1, a2) => o.tryCompare(a1, a2)) - } - - implicit def catsLawsEqForPartialOrderingExhaustive[A: ExhaustiveCheck]: Eq[PartialOrdering[A]] = { - import cats.instances.option._ - Eq.by[PartialOrdering[A], (A, A) => Option[Int]]( - (o: PartialOrdering[A]) => (a1, a2) => o.tryCompare(a1, a2) - ) - } + implicit def catsLawsEqForPartialOrderingBinCompat[A]( + implicit ev: Eq[(A, A) => Option[Int]] + ): Eq[PartialOrdering[A]] = + Eq.by[PartialOrdering[A], (A, A) => Option[Int]]((o: PartialOrdering[A]) => (a1, a2) => o.tryCompare(a1, a2)) - implicit def catsLawsEqForOrderExhaustive[A: ExhaustiveCheck]: Eq[Order[A]] = + implicit def catsLawsEqForOrderBinCompat[A](implicit ev: Eq[(A, A) => Int]): Eq[Order[A]] = Eq.by[Order[A], (A, A) => Int](o => (a1, a2) => o.compare(a1, a2)) - implicit def catsLawsEqForOrderingExhaustive[A: ExhaustiveCheck]: Eq[Ordering[A]] = + implicit def catsLawsEqForOrderingBinCompat[A](implicit ev: Eq[(A, A) => Int]): Eq[Ordering[A]] = Eq.by[Ordering[A], (A, A) => Int](o => (a1, a2) => o.compare(a1, a2)) - implicit def catsLawsEqForHashExhaustive[A: ExhaustiveCheck]: Eq[Hash[A]] = + implicit def catsLawsEqForHashBinCompat[A](implicit ev: Eq[A => Int]): Eq[Hash[A]] = Eq.by[Hash[A], A => Int](h => a => h.hash(a)) - implicit def catsLawsEqForSemigroupExhaustive[A: ExhaustiveCheck: Eq]: Eq[Semigroup[A]] = + implicit def catsLawsEqForSemigroupBinCompat[A](implicit ev: Eq[(A, A) => A]): Eq[Semigroup[A]] = Eq.by[Semigroup[A], (A, A) => A](s => (a1, a2) => s.combine(a1, a2)) - implicit def catsLawsEqForCommutativeSemigroupExhaustive[A: ExhaustiveCheck: Eq]: Eq[CommutativeSemigroup[A]] = - Eq.by[CommutativeSemigroup[A], (A, A) => (A, Boolean)]( - s => (x, y) => (s.combine(x, y), s.combine(x, y) === s.combine(y, x)) + implicit def catsLawsEqForCommutativeSemigroupBinCompat[A](implicit eqA: Eq[A], + ev: Eq[(A, A) => (A, A)]): Eq[CommutativeSemigroup[A]] = + Eq.by[CommutativeSemigroup[A], (A, A) => (A, A)](s => (x, y) => (s.combine(x, y), s.combine(y, x))) + + implicit def catsLawsEqForBandBinCompat[A](implicit ev: Eq[(A, A) => (A, A)]): Eq[Band[A]] = + Eq.by[Band[A], (A, A) => (A, A)]( + f => (x, y) => (f.combine(x, y), f.combine(f.combine(x, y), y)) + ) + + implicit def catsLawsEqForGroupBinCompat[A](implicit ev1: Eq[(A, A) => (A, Boolean)], eqA: Eq[A]): Eq[Group[A]] = + Eq.by[Group[A], (A, A) => (A, Boolean)]( + f => + (x, y) => + ( + f.combine(x, y), + f.combine(f.inverse(x), x) === f.empty && f.combine(x, f.inverse(x)) === f.empty && + f.combine(f.inverse(y), y) === f.empty && f.combine(y, f.inverse(y)) === f.empty && + f.inverse(f.empty) == f.empty + ) ) implicit def catsLawsEqForMonoid[A](implicit eqSA: Eq[Semigroup[A]], eqA: Eq[A]): Eq[Monoid[A]] = new Eq[Monoid[A]] { @@ -103,12 +111,16 @@ object eq extends DisciplineEqInstances { * they haven't been removed, but they should be considered to be deprecated, and we put them in a * lower implicit scope priority. */ -private[discipline] trait DisciplineEqInstances { +private[discipline] trait DisciplineBinCompatEqInstances { /** * Create an approximation of Eq[A => B] by generating random values for A * and comparing the application of the two functions. */ + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForFn1Exhaustive instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForFn1[A, B](implicit A: Arbitrary[A], B: Eq[B]): Eq[A => B] = new Eq[A => B] { val sampleCnt: Int = if (Platform.isJvm) 50 else 30 @@ -125,14 +137,26 @@ private[discipline] trait DisciplineEqInstances { * Create an approximation of Eq[(A, B) => C] by generating random values for A and B * and comparing the application of the two functions. */ + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForFn2BinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForFn2[A, B, C](implicit A: Arbitrary[A], B: Arbitrary[B], C: Eq[C]): Eq[(A, B) => C] = Eq.by((_: (A, B) => C).tupled)(catsLawsEqForFn1) /** `Eq[AndThen]` instance, built by piggybacking on [[catsLawsEqForFn1]]. */ + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForAndThenBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForAndThen[A, B](implicit A: Arbitrary[A], B: Eq[B]): Eq[AndThen[A, B]] = Eq.instance(catsLawsEqForFn1[A, B].eqv(_, _)) /** Create an approximation of Eq[Show[A]] by using catsLawsEqForFn1[A, String] */ + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForShowBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForShow[A: Arbitrary]: Eq[Show[A]] = Eq.by[Show[A], A => String] { showInstance => (a: A) => showInstance.show(a) @@ -142,6 +166,10 @@ private[discipline] trait DisciplineEqInstances { * Create an approximate Eq instance for some type A, by comparing * the behavior of `f(x, b)` and `f(y, b)` across many `b` samples. */ + @deprecated( + "This method is problematic and will most likely be removed in a future version of Cats. You may want to use an approach based on catsLawsEqForFn1Exhaustive instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) def sampledEq[A, B: Arbitrary, C: Eq](samples: Int)(f: (A, B) => C): Eq[A] = new Eq[A] { val gen = Arbitrary.arbitrary[B] @@ -155,23 +183,47 @@ private[discipline] trait DisciplineEqInstances { } } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForEqBinCompt instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForEq[A](implicit arbA: Arbitrary[(A, A)]): Eq[Eq[A]] = sampledEq[Eq[A], (A, A), Boolean](100) { case (e, (l, r)) => e.eqv(l, r) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForEquivBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForEquiv[A](implicit arbA: Arbitrary[(A, A)]): Eq[Equiv[A]] = sampledEq[Equiv[A], (A, A), Boolean](100) { case (e, (l, r)) => e.equiv(l, r) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForPartialOrderBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForPartialOrder[A](implicit arbA: Arbitrary[(A, A)], optIntEq: Eq[Option[Int]]): Eq[PartialOrder[A]] = sampledEq[PartialOrder[A], (A, A), Option[Int]](100) { case (p, (l, r)) => p.tryCompare(l, r) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForPartialOrderingBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForPartialOrdering[A](implicit arbA: Arbitrary[(A, A)], optIntEq: Eq[Option[Int]]): Eq[PartialOrdering[A]] = sampledEq[PartialOrdering[A], (A, A), Option[Int]](100) { case (p, (l, r)) => p.tryCompare(l, r) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForOrderBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForOrder[A](implicit arbA: Arbitrary[(A, A)]): Eq[Order[A]] = sampledEq[Order[A], (A, A), Int](100) { case (p, (l, r)) => p.compare(l, r) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForOrderingBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForOrdering[A](implicit arbA: Arbitrary[(A, A)]): Eq[Ordering[A]] = sampledEq[Ordering[A], (A, A), Int](100) { case (p, (l, r)) => p.compare(l, r) } @@ -179,6 +231,10 @@ private[discipline] trait DisciplineEqInstances { * Creates an approximation of Eq[Hash[A]] by generating 100 values for A * and comparing the application of the two hash functions. */ + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForHashBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForHash[A](implicit arbA: Arbitrary[A]): Eq[Hash[A]] = new Eq[Hash[A]] { def eqv(f: Hash[A], g: Hash[A]): Boolean = { val samples = List.fill(100)(arbA.arbitrary.sample).collect { @@ -195,11 +251,19 @@ private[discipline] trait DisciplineEqInstances { * Create an approximation of Eq[Semigroup[A]] by generating values for A * and comparing the application of the two combine functions. */ + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForSemigroupBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForSemigroup[A](implicit arbAA: Arbitrary[(A, A)], eqA: Eq[A]): Eq[Semigroup[A]] = { val instance: Eq[((A, A)) => A] = catsLawsEqForFn1[(A, A), A] Eq.by[Semigroup[A], ((A, A)) => A](f => Function.tupled((x, y) => f.combine(x, y)))(instance) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForCommutativeSemigroupBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForCommutativeSemigroup[A](implicit arbAA: Arbitrary[(A, A)], eqA: Eq[A]): Eq[CommutativeSemigroup[A]] = { implicit val eqABool: Eq[(A, Boolean)] = Eq.instance { @@ -211,6 +275,10 @@ private[discipline] trait DisciplineEqInstances { )(catsLawsEqForFn1[(A, A), (A, Boolean)]) } + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForBandBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForBand[A](implicit arbAA: Arbitrary[(A, A)], eqSA: Eq[Semigroup[A]], eqA: Eq[A]): Eq[Band[A]] = @@ -218,6 +286,10 @@ private[discipline] trait DisciplineEqInstances { f => Function.tupled((x, y) => f.combine(x, y) === f.combine(f.combine(x, y), y)) )(catsLawsEqForFn1[(A, A), Boolean]) + @deprecated( + "This instance is problematic and will most likely be removed in a future version of Cats. Use catsLawsEqForGroupBinCompat instead. See https://github.com/typelevel/cats/pull/2577 for more information.", + "1.7" + ) implicit def catsLawsEqForGroup[A](implicit arbAA: Arbitrary[(A, A)], eqMA: Eq[Monoid[A]], eqA: Eq[A]): Eq[Group[A]] = { diff --git a/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala b/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala index 50df6e6a58..f5446c6f1c 100644 --- a/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala +++ b/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala @@ -21,6 +21,9 @@ object ExhaustiveCheck { implicit val catsLawsExhaustiveCheckForBoolean: ExhaustiveCheck[Boolean] = instance(Stream(false, true)) + implicit val catsLawsExhaustiveCheckForSetBoolean: ExhaustiveCheck[Set[Boolean]] = + forSet[Boolean] + /** * Warning: the domain of (A, B) is the cross-product of the domain of `A` and the domain of `B`. */ @@ -48,4 +51,12 @@ object ExhaustiveCheck { implicit def catsLawsExhaustiveCheckForOption[A](implicit A: ExhaustiveCheck[A]): ExhaustiveCheck[Option[A]] = instance(Stream.cons(None, A.allValues.map(Some(_)))) + + /** + * Creates an `ExhaustiveCheck[Set[A]]` given an `ExhaustiveCheck[A]` by computing the powerset of + * values. Note that if there are `n` elements in the domain of `A` there will be `2^n` elements + * in the domain of `Set[A]`, so use this only on small domains. + */ + def forSet[A](implicit A: ExhaustiveCheck[A]): ExhaustiveCheck[Set[A]] = + instance(A.allValues.toSet.subsets.toStream) } diff --git a/laws/src/main/scala/cats/laws/discipline/MiniInt.scala b/laws/src/main/scala/cats/laws/discipline/MiniInt.scala index 66eb741a9f..bc193ab6c4 100644 --- a/laws/src/main/scala/cats/laws/discipline/MiniInt.scala +++ b/laws/src/main/scala/cats/laws/discipline/MiniInt.scala @@ -2,35 +2,78 @@ package cats package laws package discipline +import cats.kernel.{BoundedSemilattice, CommutativeGroup, CommutativeMonoid} import cats.instances.int._ /** - * Similar to `Int`, but with a much smaller domain. The exact range of [[MiniInt]] may be tuned - * from time to time, so consumers of this type should avoid depending on its exact range. + * Similar to `Int`, but with a much smaller domain. The exact range of [[MiniInt]] may be tuned from time to time, so + * consumers of this type should avoid depending on its exact range. + * + * `MiniInt` has integer overflow characteristics similar to `Int` (but with a smaller radix), meaning that its addition + * and multiplication are commutative and associative. */ -final class MiniInt private (val asInt: Int) extends AnyVal with Serializable +final class MiniInt private (val intBits: Int) extends AnyVal with Serializable { + import MiniInt._ + + def unary_- : MiniInt = this * negativeOne + + def toInt: Int = intBits << intShift >> intShift + + def +(o: MiniInt): MiniInt = wrapped(intBits + o.intBits) + def *(o: MiniInt): MiniInt = wrapped(intBits * o.intBits) + def |(o: MiniInt): MiniInt = wrapped(intBits | o.intBits) + def /(o: MiniInt): MiniInt = wrapped(intBits / o.intBits) + + override def toString: String = s"MiniInt(toInt=$toInt, intBits=$intBits)" +} object MiniInt { - val minIntValue: Int = -7 + val bitCount: Int = 4 + val minIntValue: Int = -8 val maxIntValue: Int = 7 + private val intShift: Int = 28 + val minValue: MiniInt = unsafeFromInt(minIntValue) + val maxValue: MiniInt = unsafeFromInt(maxIntValue) + val zero: MiniInt = unsafeFromInt(0) + val one: MiniInt = unsafeFromInt(1) + val negativeOne: MiniInt = unsafeFromInt(-1) def isInDomain(i: Int): Boolean = i >= minIntValue && i <= maxIntValue - def apply(i: Int): Option[MiniInt] = if (isInDomain(i)) Some(new MiniInt(i)) else None + def fromInt(i: Int): Option[MiniInt] = if (isInDomain(i)) Some(unsafeFromInt(i)) else None + + def wrapped(intBits: Int): MiniInt = new MiniInt(intBits & (-1 >>> intShift)) - def unsafeApply(i: Int): MiniInt = - if (isInDomain(i)) new MiniInt(i) - else throw new IllegalArgumentException(s"Expected value between $minIntValue and $maxIntValue but got $i") + def unsafeFromInt(i: Int): MiniInt = + if (isInDomain(i)) { + new MiniInt(i << intShift >>> intShift) + } else throw new IllegalArgumentException(s"Expected value between $minIntValue and $maxIntValue but got $i") - val allValues: Stream[MiniInt] = (minIntValue to maxIntValue).map(unsafeApply).toStream + val allValues: Stream[MiniInt] = (minIntValue to maxIntValue).map(unsafeFromInt).toStream implicit val catsLawsEqInstancesForMiniInt: Order[MiniInt] with Hash[MiniInt] = new Order[MiniInt] with Hash[MiniInt] { - def hash(x: MiniInt): Int = Hash[Int].hash(x.asInt) + def hash(x: MiniInt): Int = Hash[Int].hash(x.intBits) - def compare(x: MiniInt, y: MiniInt): Int = Order[Int].compare(x.asInt, y.asInt) + def compare(x: MiniInt, y: MiniInt): Int = Order[Int].compare(x.toInt, y.toInt) } implicit val catsLawsExhuastiveCheckForMiniInt: ExhaustiveCheck[MiniInt] = ExhaustiveCheck.instance(allValues) + + val miniIntAddition: CommutativeGroup[MiniInt] = new CommutativeGroup[MiniInt] { + val empty = MiniInt.zero + def combine(x: MiniInt, y: MiniInt): MiniInt = x + y + def inverse(x: MiniInt): MiniInt = -x + } + + val miniIntMultiplication: CommutativeMonoid[MiniInt] = new CommutativeMonoid[MiniInt] { + val empty = MiniInt.one + def combine(x: MiniInt, y: MiniInt): MiniInt = x * y + } + + val miniIntOr: BoundedSemilattice[MiniInt] = new BoundedSemilattice[MiniInt] { + val empty = MiniInt.zero + def combine(x: MiniInt, y: MiniInt): MiniInt = x | y + } } diff --git a/tests/src/test/scala/cats/tests/AlgebraInvariantSuite.scala b/tests/src/test/scala/cats/tests/AlgebraInvariantSuite.scala index 329ebcc56c..a0026eda08 100644 --- a/tests/src/test/scala/cats/tests/AlgebraInvariantSuite.scala +++ b/tests/src/test/scala/cats/tests/AlgebraInvariantSuite.scala @@ -4,66 +4,160 @@ package tests import cats.Invariant import cats.kernel._ import cats.kernel.laws.discipline.{SemigroupTests, MonoidTests, GroupTests, _} -import cats.laws.discipline.{InvariantMonoidalTests, InvariantSemigroupalTests, InvariantTests, SerializableTests} +import cats.laws.discipline.{ + InvariantMonoidalTests, + InvariantSemigroupalTests, + InvariantTests, + MiniInt, + SerializableTests +} +import MiniInt._ import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} class AlgebraInvariantSuite extends CatsSuite { + // working around https://github.com/typelevel/cats/issues/2701 + implicit private val eqSetBooleanTuple: Eq[(Set[Boolean], Set[Boolean])] = Eq.fromUniversalEquals + implicit private val eqSetBooleanBooleanTuple: Eq[(Set[Boolean], Boolean)] = Eq.fromUniversalEquals + + // https://github.com/typelevel/cats/issues/2725 + implicit private def commutativeMonoidForSemigroup[A]( + implicit csA: CommutativeSemigroup[A] + ): CommutativeMonoid[Option[A]] = + new CommutativeMonoid[Option[A]] { + def empty: Option[A] = None + def combine(x: Option[A], y: Option[A]): Option[A] = (x, y) match { + case (None, r) => r + case (l, None) => l + case (Some(l), Some(r)) => Some(csA.combine(l, r)) + } + } + + private def leftOptionMonoid[A]: Monoid[Option[A]] = + new Monoid[Option[A]] { + def empty: Option[A] = None + def combine(x: Option[A], y: Option[A]): Option[A] = x + } + + private def rightOptionMonoid[A]: Monoid[Option[A]] = + new Monoid[Option[A]] { + def empty: Option[A] = None + def combine(x: Option[A], y: Option[A]): Option[A] = y + } + + private val boundedSemilatticeMiniInt: BoundedSemilattice[MiniInt] = new BoundedSemilattice[MiniInt] { + def empty: MiniInt = MiniInt.zero + def combine(x: MiniInt, y: MiniInt): MiniInt = x | y + } + + private val genBoundedSemilatticeMiniInt: Gen[BoundedSemilattice[MiniInt]] = + Gen.const(miniIntOr) - val intMultiplication: CommutativeMonoid[Int] = new CommutativeMonoid[Int] { - val empty = 1 - def combine(x: Int, y: Int): Int = x * y + private val genCommutativeGroupInt: Gen[CommutativeGroup[Int]] = + Gen.const(implicitly[CommutativeGroup[Int]]) + + private val miniIntMultiplication: CommutativeMonoid[MiniInt] = new CommutativeMonoid[MiniInt] { + val empty = MiniInt.one + def combine(x: MiniInt, y: MiniInt): MiniInt = x * y } - val maxInt: Monoid[Int] = new Monoid[Int] { - val empty = Int.MinValue - def combine(x: Int, y: Int): Int = if (x > y) x else y + private val maxMiniInt: CommutativeMonoid[MiniInt] = new CommutativeMonoid[MiniInt] { + val empty = MiniInt.minValue + def combine(x: MiniInt, y: MiniInt): MiniInt = if (x > y) x else y } - val genMonoidInt: Gen[Monoid[Int]] = - Gen.oneOf(implicitly[Monoid[Int]], intMultiplication, maxInt) + private val genMonoidMiniInt: Gen[Monoid[MiniInt]] = + Gen.oneOf(miniIntAddition, miniIntMultiplication, maxMiniInt) - val genCommutativeMonoidInt: Gen[CommutativeMonoid[Int]] = - Gen.oneOf(implicitly[CommutativeMonoid[Int]], intMultiplication) + private val genMonoidOptionMiniInt: Gen[Monoid[Option[MiniInt]]] = + Gen.oneOf( + commutativeMonoidForSemigroup(miniIntAddition), + commutativeMonoidForSemigroup(miniIntMultiplication), + commutativeMonoidForSemigroup(maxMiniInt), + leftOptionMonoid[MiniInt], + rightOptionMonoid[MiniInt] + ) - val genBoundedSemilatticeSetInt: Gen[BoundedSemilattice[Set[Int]]] = - Gen.const(implicitly[BoundedSemilattice[Set[Int]]]) + private val genCommutativeMonoidMiniInt: Gen[CommutativeMonoid[MiniInt]] = + Gen.oneOf(miniIntAddition, miniIntMultiplication, maxMiniInt) - val genCommutativeGroupInt: Gen[CommutativeGroup[Int]] = - Gen.const(implicitly[CommutativeGroup[Int]]) + private val genCommutativeGroupMiniInt: Gen[CommutativeGroup[MiniInt]] = + Gen.const(miniIntAddition) + + implicit private val arbMonoidOptionMiniInt: Arbitrary[Monoid[Option[MiniInt]]] = + Arbitrary(genMonoidOptionMiniInt) + + implicit private val arbSemigroupOptionMiniInt: Arbitrary[Semigroup[Option[MiniInt]]] = + Arbitrary(genMonoidOptionMiniInt) + + implicit private val arbSemigroupMiniInt: Arbitrary[Semigroup[MiniInt]] = + Arbitrary(genMonoidMiniInt) + + implicit private val arbCommutativeMonoidMiniInt: Arbitrary[CommutativeMonoid[MiniInt]] = + Arbitrary(genCommutativeMonoidMiniInt) + + implicit private val arbCommutativeSemigroupMiniInt: Arbitrary[CommutativeSemigroup[MiniInt]] = + Arbitrary(genCommutativeMonoidMiniInt) - implicit val arbMonoidInt: Arbitrary[Monoid[Int]] = - Arbitrary(genMonoidInt) + implicit private val arbGroupMiniInt: Arbitrary[Group[MiniInt]] = + Arbitrary(genCommutativeGroupMiniInt) - implicit val arbSemigroupInt: Arbitrary[Semigroup[Int]] = - Arbitrary(genMonoidInt) + implicit private val arbCommutativeGroupMiniInt: Arbitrary[CommutativeGroup[MiniInt]] = + Arbitrary(genCommutativeGroupMiniInt) - implicit val arbCommutativeMonoidInt: Arbitrary[CommutativeMonoid[Int]] = - Arbitrary(genCommutativeMonoidInt) + private val boolAnd: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] { + val empty = true + def combine(x: Boolean, y: Boolean): Boolean = x && y + } + + private val boolOr: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] { + val empty = false + def combine(x: Boolean, y: Boolean): Boolean = x || y + } + + private val genMonoidOptionBoolean: Gen[Monoid[Option[Boolean]]] = + Gen.oneOf(commutativeMonoidForSemigroup(boolAnd), + commutativeMonoidForSemigroup(boolOr), + leftOptionMonoid[Boolean], + rightOptionMonoid[Boolean]) + + implicit private val arbMonoidOptionBoolean: Arbitrary[Monoid[Option[Boolean]]] = + Arbitrary(genMonoidOptionBoolean) + + implicit private val arbSemibroupOptionBoolean: Arbitrary[Semigroup[Option[Boolean]]] = + Arbitrary(genMonoidOptionBoolean) - implicit val arbCommutativeSemigroupInt: Arbitrary[CommutativeSemigroup[Int]] = - Arbitrary(genCommutativeMonoidInt) + private val genCommutativeMonoidBoolean: Gen[CommutativeMonoid[Boolean]] = + Gen.oneOf(boolAnd, boolOr) - implicit val arbBandSetInt: Arbitrary[Band[Set[Int]]] = - Arbitrary(genBoundedSemilatticeSetInt) + implicit private val arbCommutativeMonoidBoolean: Arbitrary[CommutativeMonoid[Boolean]] = + Arbitrary(genCommutativeMonoidBoolean) - implicit val arbSemilatticeSetInt: Arbitrary[Semilattice[Set[Int]]] = - Arbitrary(genBoundedSemilatticeSetInt) + implicit private val arbCommutativeSemigroupBoolean: Arbitrary[CommutativeSemigroup[Boolean]] = + Arbitrary(genCommutativeMonoidBoolean) - implicit val arbBoundedSemilatticeSetInt: Arbitrary[BoundedSemilattice[Set[Int]]] = - Arbitrary(genBoundedSemilatticeSetInt) + implicit private val arbBandSetMiniInt: Arbitrary[Band[MiniInt]] = + Arbitrary(genBoundedSemilatticeMiniInt) - implicit val arbGroupInt: Arbitrary[Group[Int]] = + implicit private val arbSemilatticeSetMiniInt: Arbitrary[Semilattice[MiniInt]] = + Arbitrary(genBoundedSemilatticeMiniInt) + + implicit private val arbBoundedSemilatticeSetMiniInt: Arbitrary[BoundedSemilattice[MiniInt]] = + Arbitrary(genBoundedSemilatticeMiniInt) + + implicit private val arbGroupInt: Arbitrary[Group[Int]] = Arbitrary(genCommutativeGroupInt) - implicit val arbCommutativeGroupInt: Arbitrary[CommutativeGroup[Int]] = + implicit private val arbCommutativeGroupInt: Arbitrary[CommutativeGroup[Int]] = Arbitrary(genCommutativeGroupInt) checkAll("InvariantMonoidal[Semigroup]", SemigroupTests[Int](InvariantMonoidal[Semigroup].point(0)).semigroup) checkAll("InvariantMonoidal[CommutativeSemigroup]", CommutativeSemigroupTests[Int](InvariantMonoidal[CommutativeSemigroup].point(0)).commutativeSemigroup) - checkAll("InvariantSemigroupal[Monoid]", InvariantSemigroupalTests[Monoid].invariantSemigroupal[Int, Int, Int]) + checkAll("InvariantSemigroupal[Monoid]", + InvariantSemigroupalTests[Monoid].invariantSemigroupal[Option[MiniInt], Option[Boolean], Option[Boolean]]) { val S: Semigroup[Int] = Semigroup[Int].imap(identity)(identity) @@ -90,6 +184,10 @@ class AlgebraInvariantSuite extends CatsSuite { checkAll("CommutativeMonoid[Int]", CommutativeMonoidTests[Int](S).commutativeMonoid) } + { + checkAll("CommutativeMonoid[MiniInt]", CommutativeMonoidTests[MiniInt](miniIntAddition).commutativeMonoid) + } + { val S: CommutativeGroup[Int] = CommutativeGroup[Int].imap(identity)(identity) checkAll("CommutativeGroup[Int]", CommutativeGroupTests[Int](S).commutativeGroup) @@ -110,38 +208,45 @@ class AlgebraInvariantSuite extends CatsSuite { checkAll("BoundedSemilattice[Set[Int]]", BoundedSemilatticeTests[Set[Int]](S).boundedSemilattice) } - checkAll("Invariant[Semigroup]", InvariantTests[Semigroup].invariant[Int, Int, Int]) + checkAll("Invariant[Semigroup]", InvariantTests[Semigroup].invariant[MiniInt, Boolean, Boolean]) checkAll("Invariant[Semigroup]", SerializableTests.serializable(Invariant[Semigroup])) - checkAll("Invariant[CommutativeSemigroup]", InvariantTests[CommutativeSemigroup].invariant[Int, Int, Int]) + checkAll("Invariant[CommutativeSemigroup]", InvariantTests[CommutativeSemigroup].invariant[MiniInt, Boolean, Boolean]) checkAll("Invariant[CommutativeSemigroup]", SerializableTests.serializable(Invariant[CommutativeSemigroup])) - checkAll("Invariant[Band]", InvariantTests[Band].invariant[Set[Int], Set[Int], Set[Int]]) + checkAll("Invariant[Band]", InvariantTests[Band].invariant[MiniInt, Set[Boolean], Set[Boolean]]) checkAll("Invariant[Band]", SerializableTests.serializable(Invariant[Band])) - checkAll("Invariant[Monoid]", InvariantTests[Monoid].invariant[Int, Int, Int]) + checkAll("Invariant[Monoid]", InvariantTests[Monoid].invariant[Option[MiniInt], Boolean, Boolean]) checkAll("Invariant[Monoid]", SerializableTests.serializable(Invariant[Monoid])) - checkAll("Invariant[Semilattice]", InvariantTests[Semilattice].invariant[Set[Int], Set[Int], Set[Int]]) + Eq[Band[Set[Boolean]]] + cats.laws.discipline.ExhaustiveCheck[Set[Boolean]] + Eq[(Set[Boolean], Boolean)] + Eq[(Set[Boolean], Set[Boolean] => (Set[Boolean], Boolean))] + Eq[CommutativeSemigroup[Set[Boolean]]] + checkAll("Invariant[Semilattice]", InvariantTests[Semilattice].invariant[MiniInt, Set[Boolean], Set[Boolean]]) checkAll("Invariant[Semilattice]", SerializableTests.serializable(Invariant[Semilattice])) - checkAll("Invariant[CommutativeMonoid]", InvariantTests[CommutativeMonoid].invariant[Int, Int, Int]) + checkAll("Invariant[CommutativeMonoid]", InvariantTests[CommutativeMonoid].invariant[MiniInt, Boolean, Boolean]) checkAll("Invariant[CommutativeMonoid]", SerializableTests.serializable(Invariant[CommutativeMonoid])) - checkAll("Invariant[BoundedSemilattice]", InvariantTests[BoundedSemilattice].invariant[Set[Int], Set[Int], Set[Int]]) + checkAll("Invariant[BoundedSemilattice]", + InvariantTests[BoundedSemilattice].invariant[MiniInt, Set[Boolean], Set[Boolean]]) checkAll("Invariant[BoundedSemilattice]", SerializableTests.serializable(Invariant[BoundedSemilattice])) - checkAll("Invariant[Group]", InvariantTests[Group].invariant[Int, Int, Int]) + checkAll("Invariant[Group]", InvariantTests[Group].invariant[MiniInt, Boolean, Boolean]) checkAll("Invariant[Group]", SerializableTests.serializable(Invariant[Group])) - checkAll("Invariant[CommutativeGroup]", InvariantTests[CommutativeGroup].invariant[Int, Int, Int]) + checkAll("Invariant[CommutativeGroup]", InvariantTests[CommutativeGroup].invariant[MiniInt, Boolean, Boolean]) checkAll("Invariant[CommutativeGroup]", SerializableTests.serializable(Invariant[CommutativeGroup])) - checkAll("InvariantMonoidal[Semigroup]", InvariantMonoidalTests[Semigroup].invariantMonoidal[Int, Int, Int]) + checkAll("InvariantMonoidal[Semigroup]", + InvariantMonoidalTests[Semigroup].invariantMonoidal[Option[MiniInt], Option[Boolean], Option[Boolean]]) checkAll("InvariantMonoidal[Semigroup]", SerializableTests.serializable(InvariantMonoidal[Semigroup])) checkAll("InvariantMonoidal[CommutativeSemigroup]", - InvariantMonoidalTests[CommutativeSemigroup].invariantMonoidal[Int, Int, Int]) + InvariantMonoidalTests[CommutativeSemigroup].invariantMonoidal[MiniInt, Boolean, Boolean]) checkAll("InvariantMonoidal[CommutativeSemigroup]", SerializableTests.serializable(InvariantMonoidal[CommutativeSemigroup])) diff --git a/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala b/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala new file mode 100644 index 0000000000..2e6390c48e --- /dev/null +++ b/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala @@ -0,0 +1,173 @@ +package cats +package tests + +import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.{ExhaustiveCheck, InvariantMonoidalTests, MiniInt, SerializableTests} +import cats.implicits._ +import cats.Eq +import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests} +import org.scalacheck.{Arbitrary, Gen} + +object BinCodecInvariantMonoidalSuite { + final case class MiniList[+A] private (val toList: List[A]) extends AnyVal { + import MiniList.truncated + + /** + * Returns a new MiniList with a modified underlying List. Note that this truncates the returned List to ensure that + * it fits into the allowable MiniInt range. + */ + def mod[B](f: List[A] => List[B]): MiniList[B] = truncated(f(toList)) + + /** + * Append a `MiniList`. + * + * Note: this will trim the resulting list to respect the maximum list length. + */ + def ++[AA >: A](o: MiniList[AA]): MiniList[AA] = mod(_ ++ o.toList) + } + + object MiniList { + val maxLength: Int = 6 + + val nil: MiniList[Nothing] = MiniList(Nil) + def empty[A]: MiniList[A] = nil + + def truncated[A](l: List[A]): MiniList[A] = MiniList(l.take(maxLength)) + + def unsafe[A](l: List[A]): MiniList[A] = { + val inputLength = l.size + if (inputLength > maxLength) + throw new IllegalArgumentException( + s"MiniList.unsafe called with list of size $inputLength, but $maxLength is the maximum allowed size." + ) + else MiniList(l) + } + + def one[A](a: A): MiniList[A] = MiniList(a :: Nil) + + implicit def eqForMiniList[A: Eq]: Eq[MiniList[A]] = Eq.by(_.toList) + + implicit val exhaustiveCheckForMiniListBoolean: ExhaustiveCheck[MiniList[Boolean]] = + ExhaustiveCheck.instance( + for { + length <- (0 to maxLength).toStream + boolList <- List(false, true).replicateA(length).toStream + } yield MiniList.unsafe(boolList) + ) + } + + /** A small amount of binary bits */ + type Bin = MiniList[Boolean] + + /** + * Type class to read and write objects of type A to binary. + * + * Obeys `forAll { (c: BinCodec[A], a: A) => c.read(c.writes(a)) == (Some(a), List())`, + * under the assumtion that `imap(f, g)` is always called with `f` and `g` such that + * `forAll { (a: A) => g(f(a)) == a }`. + */ + trait BinCodec[A] extends Serializable { self => + + /** Reads the first value of a Bin, returning an optional value of type `A` and the remaining Bin. */ + def read(s: Bin): (Option[A], Bin) + + /** Writes a value of type `A` to Bin format. */ + def write(a: A): Bin + } + + object BinCodec { + // In tut/invariantmonoidal.md pure, product and imap are defined in + // their own trait to be introduced one by one, + trait CCPure { + def unit: BinCodec[Unit] = new BinCodec[Unit] { + def read(s: Bin): (Option[Unit], Bin) = (Some(()), s) + def write(a: Unit): Bin = MiniList.empty + } + } + + trait CCProduct { + def product[A, B](fa: BinCodec[A], fb: BinCodec[B]): BinCodec[(A, B)] = + new BinCodec[(A, B)] { + def read(s: Bin): (Option[(A, B)], Bin) = { + val (a1, s1) = fa.read(s) + val (a2, s2) = fb.read(s1) + ((a1, a2).mapN(_ -> _), s2) + } + + def write(a: (A, B)): Bin = + fa.write(a._1) ++ fb.write(a._2) + } + } + + trait CCImap { + def imap[A, B](fa: BinCodec[A])(f: A => B)(g: B => A): BinCodec[B] = + new BinCodec[B] { + def read(s: Bin): (Option[B], Bin) = { + val (a1, s1) = fa.read(s) + (a1.map(f), s1) + } + + def write(a: B): Bin = + fa.write(g(a)) + } + } + + implicit val binCodecIsInvariantMonoidal: InvariantMonoidal[BinCodec] = + new InvariantMonoidal[BinCodec] with CCPure with CCProduct with CCImap + } + + def genBinCodecForExhaustive[A](implicit exA: ExhaustiveCheck[A]): Gen[BinCodec[A]] = + for { + bitCount <- Gen.oneOf(1, 2, 3) + shuffleSeed <- Gen.choose(Long.MinValue, Long.MaxValue) + } yield { + val binValues: Stream[Bin] = Stream(false, true).replicateA(bitCount).map(MiniList.unsafe(_)) + val pairs: List[(A, Bin)] = new scala.util.Random(seed = shuffleSeed).shuffle(exA.allValues).toList.zip(binValues) + val aToBin: Map[A, Bin] = pairs.toMap + val binToA: Map[Bin, A] = pairs.map(_.swap).toMap + + new BinCodec[A] { + def read(s: Bin): (Option[A], Bin) = + (binToA.get(s.mod(_.take(bitCount))), s.mod(_.drop(bitCount))) + + def write(a: A): Bin = + aToBin.getOrElse(a, MiniList.empty) + + override def toString: String = s"BinCodec($pairs)" + } + } + + implicit val arbMiniIntCodec: Arbitrary[BinCodec[MiniInt]] = + Arbitrary(genBinCodecForExhaustive[MiniInt]) + + implicit val arbBooleanCodec: Arbitrary[BinCodec[Boolean]] = + Arbitrary(genBinCodecForExhaustive[Boolean]) + + implicit def binCodecsEq[A: Eq: ExhaustiveCheck]: Eq[BinCodec[A]] = { + val writeEq: Eq[BinCodec[A]] = Eq.by[BinCodec[A], A => Bin](_.write) + + val readEq: Eq[BinCodec[A]] = Eq.by[BinCodec[A], Bin => (Option[A], Bin)](_.read) + Eq.and(writeEq, readEq) + } +} + +class BinCodecInvariantMonoidalSuite extends CatsSuite { + // Eveything is defined in a companion object to be serializable. + import BinCodecInvariantMonoidalSuite._ + + checkAll("InvariantMonoidal[BinCodec]", InvariantMonoidalTests[BinCodec].invariantMonoidal[MiniInt, MiniInt, MiniInt]) + checkAll("InvariantMonoidal[BinCodec]", SerializableTests.serializable(InvariantMonoidal[BinCodec])) + + { + implicit val miniIntMonoid: Monoid[MiniInt] = MiniInt.miniIntAddition + implicit val binMonoid = InvariantMonoidal.monoid[BinCodec, MiniInt] + checkAll("InvariantMonoidal[BinCodec].monoid", MonoidTests[BinCodec[MiniInt]].monoid) + } + + { + implicit val miniIntSemigroup: Semigroup[MiniInt] = MiniInt.miniIntAddition + implicit val binSemigroup = InvariantSemigroupal.semigroup[BinCodec, MiniInt] + checkAll("InvariantSemigroupal[BinCodec].semigroup", SemigroupTests[BinCodec[MiniInt]].semigroup) + } +} diff --git a/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalSuite.scala b/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalSuite.scala deleted file mode 100644 index b828cacb0a..0000000000 --- a/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalSuite.scala +++ /dev/null @@ -1,108 +0,0 @@ -package cats -package tests - -import cats.laws.discipline.eq.catsLawsEqForFn1 -import cats.laws.discipline.{InvariantMonoidalTests, SerializableTests} -import cats.instances.all._ -import cats.syntax.apply._ -import cats.Eq -import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests} -import org.scalacheck.{Arbitrary, Gen} - -object CsvCodecInvariantMonoidalSuite { - type CSV = List[String] - - /** - * Type class to read and write objects of type A to CSV. - * - * Obeys `forAll { (c: CsvCodec[A], a: A) => c.read(c.writes(a)) == (Some(a), List())`, - * under the assumtion that `imap(f, g)` is always called with `f` and `g` such that - * `forAll { (a: A) => g(f(a)) == a }`. - */ - trait CsvCodec[A] extends Serializable { self => - - /** Reads the first value of a CSV, returning an optional value of type `A` and the remaining CSV. */ - def read(s: CSV): (Option[A], CSV) - - /** Writes a value of type `A` to CSV format. */ - def write(a: A): CSV - } - - object CsvCodec { - // In tut/invariantmonoidal.md pure, product and imap are defined in - // their own trait to be introduced one by one, - trait CCPure { - def unit: CsvCodec[Unit] = new CsvCodec[Unit] { - def read(s: CSV): (Option[Unit], CSV) = (Some(()), s) - def write(a: Unit): CSV = List.empty - } - } - - trait CCProduct { - def product[A, B](fa: CsvCodec[A], fb: CsvCodec[B]): CsvCodec[(A, B)] = - new CsvCodec[(A, B)] { - def read(s: CSV): (Option[(A, B)], CSV) = { - val (a1, s1) = fa.read(s) - val (a2, s2) = fb.read(s1) - ((a1, a2).mapN(_ -> _), s2) - } - - def write(a: (A, B)): CSV = - fa.write(a._1) ++ fb.write(a._2) - } - } - - trait CCImap { - def imap[A, B](fa: CsvCodec[A])(f: A => B)(g: B => A): CsvCodec[B] = - new CsvCodec[B] { - def read(s: CSV): (Option[B], CSV) = { - val (a1, s1) = fa.read(s) - (a1.map(f), s1) - } - - def write(a: B): CSV = - fa.write(g(a)) - } - } - - implicit val csvCodecIsInvariantMonoidal: InvariantMonoidal[CsvCodec] = - new InvariantMonoidal[CsvCodec] with CCPure with CCProduct with CCImap - } - - def numericSystemCodec(base: Int): CsvCodec[Int] = - new CsvCodec[Int] { - def read(s: CSV): (Option[Int], CSV) = - (s.headOption.flatMap(head => scala.util.Try(Integer.parseInt(head, base)).toOption), s.drop(1)) - - def write(a: Int): CSV = - List(Integer.toString(a, base)) - } - - implicit val arbNumericSystemCodec: Arbitrary[CsvCodec[Int]] = - Arbitrary(Gen.choose(2, 16).map(numericSystemCodec)) - - implicit def csvCodecsEq[A](implicit a: Arbitrary[A], e: Eq[A]): Eq[CsvCodec[A]] = { - val writeEq: Eq[CsvCodec[A]] = Eq.by[CsvCodec[A], A => CSV](_.write)(catsLawsEqForFn1[A, CSV]) - val readEq: Eq[CsvCodec[A]] = - Eq.by[CsvCodec[A], CSV => (Option[A], CSV)](_.read)(catsLawsEqForFn1[CSV, (Option[A], CSV)]) - Eq.and(writeEq, readEq) - } -} - -class CsvCodecInvariantMonoidalSuite extends CatsSuite { - // Eveything is defined in a companion object to be serializable. - import CsvCodecInvariantMonoidalSuite._ - - checkAll("InvariantMonoidal[CsvCodec]", InvariantMonoidalTests[CsvCodec].invariantMonoidal[Int, Int, Int]) - checkAll("InvariantMonoidal[CsvCodec]", SerializableTests.serializable(InvariantMonoidal[CsvCodec])) - - { - implicit val csvMonoid = InvariantMonoidal.monoid[CsvCodec, Int] - checkAll("InvariantMonoidal[CsvCodec].monoid", MonoidTests[CsvCodec[Int]].monoid) - } - - { - implicit val csvSemigroup = InvariantSemigroupal.semigroup[CsvCodec, Int] - checkAll("InvariantSemigroupal[CsvCodec].semigroup", SemigroupTests[CsvCodec[Int]].semigroup) - } -} diff --git a/tests/src/test/scala/cats/tests/MiniIntSuite.scala b/tests/src/test/scala/cats/tests/MiniIntSuite.scala new file mode 100644 index 0000000000..a63d690931 --- /dev/null +++ b/tests/src/test/scala/cats/tests/MiniIntSuite.scala @@ -0,0 +1,48 @@ +package cats +package tests + +import cats.laws.discipline.MiniInt +import MiniInt._ +import cats.laws.discipline.arbitrary._ +import cats.kernel.{BoundedSemilattice, CommutativeGroup, CommutativeMonoid} +import cats.kernel.laws.discipline._ + +import org.scalacheck.Gen + +class MiniIntSuite extends CatsSuite { + checkAll("MiniInt", OrderTests[MiniInt].order) + checkAll("Order[MiniInt]", SerializableTests.serializable(Order[MiniInt])) + + checkAll("MiniInt", HashTests[MiniInt].hash) + checkAll("Hash[MiniInt]", SerializableTests.serializable(Hash[MiniInt])) + + { + implicit val g: CommutativeGroup[MiniInt] = miniIntAddition + checkAll("MiniInt addition", CommutativeGroupTests[MiniInt].commutativeGroup) + checkAll("CommutativeGroup[MiniInt] addition", SerializableTests.serializable(miniIntAddition)) + } + + { + implicit val m: CommutativeMonoid[MiniInt] = miniIntMultiplication + checkAll("MiniInt addition", CommutativeMonoidTests[MiniInt].commutativeMonoid) + checkAll("CommutativeMonoid[MiniInt] multiplication", SerializableTests.serializable(miniIntMultiplication)) + } + + { + implicit val b: BoundedSemilattice[MiniInt] = miniIntOr + checkAll("MiniInt |", BoundedSemilatticeTests[MiniInt].boundedSemilattice) + checkAll("BoundedSemilattice[MiniInt] |", SerializableTests.serializable(miniIntOr)) + } + + test("int roundtrip") { + forAll { i: MiniInt => + MiniInt.fromInt(i.toInt) should ===(Some(i)) + } + } + + test("int bounds") { + forAll(Gen.chooseNum(MiniInt.minIntValue, MiniInt.maxIntValue)) { i: Int => + MiniInt.fromInt(i).map(_.toInt) should ===(Some(i)) + } + } +}