diff --git a/kernel-laws/shared/src/main/scala/cats/kernel/laws/EnumerableLaws.scala b/kernel-laws/shared/src/main/scala/cats/kernel/laws/EnumerableLaws.scala index 4c87eee4d7..25cae5ab3f 100644 --- a/kernel-laws/shared/src/main/scala/cats/kernel/laws/EnumerableLaws.scala +++ b/kernel-laws/shared/src/main/scala/cats/kernel/laws/EnumerableLaws.scala @@ -22,6 +22,66 @@ package cats.kernel package laws +trait EnumerableLaws[A] extends PartialNextLaws[A] with PartialPreviousLaws[A] { + implicit def En: Enumerable[A] // Not E to avoid conflict with + // PartialOrderLaws + + // Note, we use integer numbers as a proxy for the natural numbers + // here. Since integer numbers have a bijective, e.g. one to one, + // correspondence with the natural numbers, and we have no good + // representation for a true Natural number in cats or the Scala stdlib. + + def injectiveToNaturalNumbers(xs: Set[A]): IsEq[Int] = + xs.map(En.fromEnum).size <-> xs.size + + def hasTheSameOrderAsBigInt(x: A, y: A): IsEq[Comparison] = + En.order.comparison(x, y) <-> Order[BigInt].comparison(En.fromEnum(x), En.fromEnum(y)) + + def bigIntHasTheSameOrderAsA(xi: BigInt, yi: BigInt): IsEq[Boolean] = + En.toEnumOpt(xi).flatMap(x => + En.toEnumOpt(yi).map(y => + Order[BigInt].comparison(xi, yi) == En.order.comparison(x, y) + ) + ).fold( + true <-> true + )( + _ <-> true + ) +} + +object EnumerableLaws { + def apply[A](implicit ev: Enumerable[A]): EnumerableLaws[A] = + new EnumerableLaws[A] { + override implicit val E: Order[A] = ev.order + override implicit val En: Enumerable[A] = ev + override implicit val N: PartialNext[A] = ev + override implicit val P: PartialPrevious[A] = ev + } +} + +trait BoundlessEnumerableLaws[A] extends EnumerableLaws[A] { + implicit def En: BoundlessEnumerable[A] // Not E to avoid conflict with + // PartialOrderLaws + + // Note, we use integer numbers as a proxy for the natural numbers + // here. Since integer numbers have a bijective, e.g. one to one, + // correspondence with the natural numbers, and we have no good + // representation for a true Natural number in cats or the Scala stdlib. + + def bijectiveToNaturalNumbers(xs: Set[BigInt]): IsEq[Int] = + xs.map(En.toEnum).size <-> xs.size +} + +object BoundlessEnumerableLaws { + def apply[A](implicit ev: BoundlessEnumerable[A]): BoundlessEnumerableLaws[A] = + new BoundlessEnumerableLaws[A] { + override implicit val E: Order[A] = ev.order + override implicit val En: BoundlessEnumerable[A] = ev + override implicit val N: PartialNext[A] = ev + override implicit val P: PartialPrevious[A] = ev + } +} + trait PartialPreviousLaws[A] extends PartialOrderLaws[A] { implicit def P: PartialPrevious[A] diff --git a/kernel-laws/shared/src/main/scala/cats/kernel/laws/discipline/EnumerableTests.scala b/kernel-laws/shared/src/main/scala/cats/kernel/laws/discipline/EnumerableTests.scala index d89822ff63..52b99b9c72 100644 --- a/kernel-laws/shared/src/main/scala/cats/kernel/laws/discipline/EnumerableTests.scala +++ b/kernel-laws/shared/src/main/scala/cats/kernel/laws/discipline/EnumerableTests.scala @@ -28,6 +28,44 @@ import cats.kernel.instances.boolean._ import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll +trait EnumerableTests[A] extends PartialNextTests[A] with PartialPreviousTests[A] { + def laws: EnumerableLaws[A] + + def enumerable(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqOA: Eq[Option[A]], eqA: Eq[A]): RuleSet = + new RuleSet { + override val name: String = "enumerable" + override val bases: Seq[(String, RuleSet)] = Nil + override val parents: Seq[RuleSet] = Seq(partialNext, partialPrevious, partialOrder) + override val props: Seq[(String, Prop)] = Seq( + "injective to the natural numbers" -> forAll(laws.injectiveToNaturalNumbers _) + ) + } +} + +object BoundlessEnumerableTests { + def apply[A: BoundlessEnumerable]: BoundlessEnumerableTests[A] = + new BoundlessEnumerableTests[A] { def laws: BoundlessEnumerableLaws[A] = BoundlessEnumerableLaws[A] } +} + +trait BoundlessEnumerableTests[A] extends EnumerableTests[A] { + def laws: BoundlessEnumerableLaws[A] + + def boundlessEnumerable(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqOA: Eq[Option[A]], eqA: Eq[A]): RuleSet = + new RuleSet { + override val name: String = "boundlessEnumerable" + override val bases: Seq[(String, RuleSet)] = Nil + override val parents: Seq[RuleSet] = Seq(partialNext, partialPrevious, partialOrder, enumerable) + override val props: Seq[(String, Prop)] = Seq( + "bijective to the natural numbers" -> forAll(laws.bijectiveToNaturalNumbers _) + ) + } +} + +object EnumerableTests { + def apply[A: Enumerable]: EnumerableTests[A] = + new EnumerableTests[A] { def laws: EnumerableLaws[A] = EnumerableLaws[A] } +} + trait PartialNextTests[A] extends PartialOrderTests[A] { def laws: PartialNextLaws[A] @@ -56,6 +94,7 @@ trait PartialPreviousTests[A] extends PartialOrderTests[A] { } +@deprecated(message = "Please use BoundableEnumerable and BoundableEnumerableTests", since = "2.10.0") trait BoundedEnumerableTests[A] extends OrderTests[A] with PartialNextTests[A] with PartialPreviousTests[A] { def laws: BoundedEnumerableLaws[A] @@ -81,6 +120,7 @@ trait BoundedEnumerableTests[A] extends OrderTests[A] with PartialNextTests[A] w } object BoundedEnumerableTests { + @deprecated(message = "Please use BoundableEnumerable and BoundableEnumerableTests", since = "2.10.0") def apply[A: BoundedEnumerable]: BoundedEnumerableTests[A] = new BoundedEnumerableTests[A] { def laws: BoundedEnumerableLaws[A] = BoundedEnumerableLaws[A] } } diff --git a/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala index 208a0410dd..7f94b84e92 100644 --- a/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala @@ -30,6 +30,7 @@ import Prop.forAll import Arbitrary.arbitrary import cats.kernel.instances.all.catsKernelStdOrderForDeadline +import scala.annotation.nowarn import scala.concurrent.duration.{Deadline, Duration, FiniteDuration} import scala.collection.immutable.{BitSet, Queue, SortedMap, SortedSet} import scala.util.Random @@ -211,22 +212,26 @@ class Tests extends TestsConfig with DisciplineSuite { checkAll("UpperBounded[FiniteDuration]", UpperBoundedTests[FiniteDuration].upperBounded) checkAll("UpperBounded[UUID]", UpperBoundedTests[UUID].upperBounded) - checkAll("BoundedEnumerable[Unit]", BoundedEnumerableTests[Unit].boundedEnumerable) - checkAll("BoundedEnumerable[Boolean]", BoundedEnumerableTests[Boolean].boundedEnumerable) - checkAll("BoundedEnumerable[Byte]", BoundedEnumerableTests[Byte].boundedEnumerable) - checkAll("BoundedEnumerable[Short]", BoundedEnumerableTests[Short].boundedEnumerable) - checkAll("BoundedEnumerable[Int]", BoundedEnumerableTests[Int].boundedEnumerable) - checkAll("BoundedEnumerable[Char]", BoundedEnumerableTests[Char].boundedEnumerable) - checkAll("BoundedEnumerable[Long]", BoundedEnumerableTests[Long].boundedEnumerable) - checkAll("BoundedEnumerable.reverse(BoundedEnumerable[Int])", + checkAll("Enumerable[Int]", EnumerableTests[Int].enumerable) + + checkAll("BoundlessEnumerable[BigInt]", BoundlessEnumerableTests[BigInt].boundlessEnumerable) + + (checkAll("BoundedEnumerable[Unit]", BoundedEnumerableTests[Unit].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable[Boolean]", BoundedEnumerableTests[Boolean].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable[Byte]", BoundedEnumerableTests[Byte].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable[Short]", BoundedEnumerableTests[Short].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable[Int]", BoundedEnumerableTests[Int].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable[Char]", BoundedEnumerableTests[Char].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable[Long]", BoundedEnumerableTests[Long].boundedEnumerable): @nowarn("msg=BoundableEnumerable")) + (checkAll("BoundedEnumerable.reverse(BoundedEnumerable[Int])", BoundedEnumerableTests(BoundedEnumerable.reverse(BoundedEnumerable[Int])).boundedEnumerable - ) - checkAll( + ): @nowarn("msg=BoundableEnumerable")) + (checkAll( "BoundedEnumerable.reverse(BoundedEnumerable.reverse(BoundedEnumerable[Int]))", BoundedEnumerableTests( BoundedEnumerable.reverse(BoundedEnumerable.reverse(BoundedEnumerable[Int])) ).boundedEnumerable - ) + ): @nowarn("msg=BoundableEnumerable")) checkAll("Monoid[String]", MonoidTests[String].monoid) checkAll("Monoid[String]", SerializableTests.serializable(Monoid[String])) diff --git a/kernel/src/main/scala-2.12/cats/kernel/EnumerableCompat.scala b/kernel/src/main/scala-2.12/cats/kernel/EnumerableCompat.scala index 3f86db3bd4..e063e7422b 100644 --- a/kernel/src/main/scala-2.12/cats/kernel/EnumerableCompat.scala +++ b/kernel/src/main/scala-2.12/cats/kernel/EnumerableCompat.scala @@ -25,11 +25,13 @@ package kernel import scala.{specialized => sp} import scala.collection.immutable.Stream +@deprecated(message = "Please use Enumerable instead.", since = "2.10.0") trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with UpperBounded[A] { /** * Enumerate the members in descending order. */ + @deprecated(message = "Please use Enumerable.membersDescending.", since = "2.10.0") def membersDescending: Stream[A] = { def loop(a: A): Stream[A] = partialPrevious(a) match { @@ -41,11 +43,13 @@ trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with Partial } +@deprecated(message = "Please use Enumerable instead.", since = "2.10.0") trait PartialNextLowerBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with LowerBounded[A] { /** * Enumerate the members in ascending order. */ + @deprecated(message = "Please use Enumerable.membersAscending.", since = "2.10.0") def membersAscending: Stream[A] = { def loop(a: A): Stream[A] = partialNext(a) match { diff --git a/kernel/src/main/scala-2.12/cats/kernel/ScalaVersionSpecificLazyListCompat.scala b/kernel/src/main/scala-2.12/cats/kernel/ScalaVersionSpecificLazyListCompat.scala new file mode 100644 index 0000000000..cbb0cda3c3 --- /dev/null +++ b/kernel/src/main/scala-2.12/cats/kernel/ScalaVersionSpecificLazyListCompat.scala @@ -0,0 +1,9 @@ +package cats +package kernel + +private[kernel] object ScalaVersionSpecificLazyListCompat extends LazyListCompatBase { + override final type T[A] = scala.collection.immutable.Stream[A] + + override final def apply[A](a: A*): T[A] = + scala.collection.immutable.Stream.apply[A](a: _*) +} diff --git a/kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala b/kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala index 8a782e5ae3..4de5c8a05b 100644 --- a/kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala +++ b/kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala @@ -25,11 +25,13 @@ package kernel import scala.{specialized => sp} import scala.collection.immutable.LazyList +@deprecated(message = "Please use UpperBoundableEnumerable", since = "2.10.0") trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with UpperBounded[A] { /** * Enumerate the members in descending order. */ + @deprecated(message = "Please use UpperBoundableEnumerable.enumFromMax.", since = "2.10.0") def membersDescending: LazyList[A] = { def loop(a: A): LazyList[A] = partialPrevious(a) match { @@ -41,11 +43,13 @@ trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with Partial } +@deprecated(message = "Please use LowerBoundableEnumerable", since = "2.10.0") trait PartialNextLowerBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with LowerBounded[A] { /** * Enumerate the members in ascending order. */ + @deprecated(message = "Please use LowerBoundableEnumerable.enumFromMin.", since = "2.10.0") def membersAscending: LazyList[A] = { def loop(a: A): LazyList[A] = partialNext(a) match { diff --git a/kernel/src/main/scala-2.13+/cats/kernel/ScalaVersionSpecificLazyListCompat.scala b/kernel/src/main/scala-2.13+/cats/kernel/ScalaVersionSpecificLazyListCompat.scala new file mode 100644 index 0000000000..494698fb99 --- /dev/null +++ b/kernel/src/main/scala-2.13+/cats/kernel/ScalaVersionSpecificLazyListCompat.scala @@ -0,0 +1,9 @@ +package cats +package kernel + +private[kernel] object ScalaVersionSpecificLazyListCompat extends LazyListCompatBase { + override final type T[A] = scala.collection.immutable.LazyList[A] + + override final def apply[A](a: A*): T[A] = + scala.collection.immutable.LazyList.apply[A](a: _*) +} diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index d287113d27..d4c9069d65 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -22,7 +22,271 @@ package cats package kernel +import scala.annotation.tailrec import scala.{specialized => sp} +import cats.kernel.{ScalaVersionSpecificLazyListCompat => LazyListLike} + +/** A typeclass for types which are countable. Formally this means that values + * can be mapped on to the natural numbers. + * + * Because Enumerable types may be mapped to the natural numbers, being an + * instance of `Enumerable` implies having a total ordering, e.g. an `Order` + * instance. It also implies having a `PartialNext` and `PartialPrevious` as + * all representations of the countable numbers, or a subset there of, have + * `PartialNext` and `PartialPrevious`. + * + * Instances of `Enumerable` require that the if `Order[A].comparison(x, y) + * <-> Order[BigInt](fromEnum(x), fromEnum(y))`. The ordering of a value of + * `A` corresponds to the ordering of the `BigInt` mapping of that `A`. This + * is because all of the useful functions defined by `Enumerable` require + * this correspondence to provide their utility. For example, they use the + * difference between two elements of `A` mapped onto `BigInt` to understand + * the step between enumerated values. + * + * @note Types which are countable can be both finitely countable and + * infinitely countable. The canonical example of this are the natural + * numbers themselves. They are countable, but are infinite. + * + * @see [[https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html]] + * @see [[https://en.wikipedia.org/wiki/Countable_set]] + */ +trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ + def order: Order[A] + override final def partialOrder: PartialOrder[A] = order + + /** Convert a value of `A` into its corresponding integer representation. + */ + def fromEnum(a: A): BigInt + + /** Attempt to convert a `BigInt` into its corresponding representation in + * this enumeration, yielding `None` if the given `BigInt` is outside the + * domain of this enumeration. + */ + def toEnumOpt(i: BigInt): Option[A] + + /** The fundamental function in the `Enumerable` class. Given a `first` + * element, a second element, and an optional `last` element, enumerate the + * values between `first` and `last` (or `MaxValue` or infinity), the step + * between the first and second element as the step between all elements. + * + * {{{ + * scala> Enumerable[Int].enumFromThenToOpt(1, 3, Some(11)).toList + * val res0: List[Int] = List(1, 3, 5, 7, 9, 11) + * }}} + * + * @note If the last element is defined, and the second element is less + * than the last element, then the last element will not be part of + * the result. + * + * @note The last element will only be included in the enumerated result if + * it aligns with the step. For example, `enumFromThenToOpt(1, 3, + * 6).toList`, would be `List(1, 3, 5)`. + * + * {{{ + * scala> Enumerable[Int].enumFromThenToOpt(1, 2, Some(1)).toList + * val res0: List[Int] = List(1) + * }}} + * + * All other enum like functions can be expressed in terms of this + * function. + */ + def enumFromThenToOpt(first: A, second: A, last: Option[A]): LazyListLike.T[A] = { + val Zero: BigInt = BigInt(0) + val increment: BigInt = fromEnum(second) - fromEnum(first) + + def loop(i: A): LazyListLike.T[A] = + if (increment > Zero) { + // forwards + partialNextBy(i, increment) match { + case Some(next) => + if (last.fold(false)(order.gt(next, _))) { + LazyListLike.empty[A] + } else { + next #:: loop(next) + } + case _ => + LazyListLike.empty[A] + } + } else { + // backwards or zero + partialPreviousBy(i, increment.abs) match { + case Some(next) => + if (last.fold(false)(order.lt(next, _))) { + LazyListLike.empty + } else { + next #:: loop(next) + } + case _ => + LazyListLike.empty + } + } + + last match { + case Some(last) => + order.compare(first, last) match { + case result if result < Zero => + if (increment < Zero) { + LazyListLike.empty[A] + } else { + first #:: loop(first) + } + case result if result > Zero => + if (increment > Zero) { + LazyListLike.empty[A] + } else { + first #:: loop(first) + } + case _ => + first #:: loop(first) + } + case _ => + first #:: loop(first) + } + } + + def enumFromByToOpt(first: A, step: BigInt, last: Option[A]): LazyListLike.T[A] = + toEnumOpt(step).fold( + LazyListLike.empty[A] + )(second => + enumFromThenToOpt(first, second, last) + ) + + /** Given a `first` element, a second element, and a last, enumerate the + * values between `first` and `last`, using a step between the `first` and + * the `second` element as step between all elements. + * + * {{{ + * scala> Enumerable[Int].enumFromThenTo(1, 3, 11).toList + * val res0: List[Int] = List(1, 3, 5, 7, 9, 11) + * }}} + * + * @see [[#enumFromThenToOpt]] + */ + def enumFromThenTo(first: A, second: A, last: A): LazyListLike.T[A] = + enumFromThenToOpt(first, second, Some(last)) + + def enumFromByTo(first: A, step: BigInt, last: A): LazyListLike.T[A] = + enumFromByToOpt(first, step, Some(last)) + + /** Given a first element and a last element, enumerate the + * values between `first` and `last`, using a step of 1. + * + * {{{ + * scala> Enumerable[Int].enumFromTo(1, 5).toList + * val res0: List[Int] = List(1, 2, 3, 4, 5) + * }}} + */ + def enumFromTo(first: A, last: A): LazyListLike.T[A] = + partialNext(first) match { + case Some(by) => + enumFromThenTo(first, by, last) + case _ => + if (order.lteqv(first, last)) { + LazyListLike(first) + } else { + LazyListLike.empty + } + } + + /** Given a first element and second element, enumerate all values in the + * domain starting at first using the step between first and second as the + * step between all elements. If the domain is infinite, e.g. natural + * numbers or integers, then this will be an infinite result. + * + * {{{ + * scala> Enumerable[Int].enumFromThen(Int.MaxValue - 5, Int.MaxValue - 4).toList + * val res0: List[Int] = List(2147483642, 2147483643, 2147483644, 2147483645, 2147483646, 2147483647) + * }}} + */ + def enumFromThen(first: A, second: A): LazyListLike.T[A] = + enumFromThenToOpt(first, second, None) + + def enumFromBy(first: A, by: BigInt): LazyListLike.T[A] = + enumFromByToOpt(first, by, None) + + /** Given a first element, enumerate all values in the domain starting at + * first using a step of difference between the next element and the first + * element. If the domain is infinite, e.g. natural numbers or integers, + * then this will be an infinite result. + * + * {{{ + * scala> Enumerable[Int].enumFrom(Int.MaxValue - 5).toList + * val res0: List[Int] = List(2147483642, 2147483643, 2147483644, 2147483645, 2147483646, 2147483647) + * }}} + */ + def enumFrom(first: A): LazyListLike.T[A] = + partialNext(first) match { + case Some(by) => + enumFromThen(first, by) + case _ => + LazyListLike(first) + } + + /** All members of this enumerable starting at the min bound and continuing + * upward. + * + * @note If the type has no max bound, then this will be an infinite + * list. + */ + def enumFromMin(implicit A: LowerBounded[A]): LazyListLike.T[A] = + partialNext(A.minBound).fold( + LazyListLike(A.minBound) + )(next => + enumFromThen(A.minBound, next) + ) + + /** All members of this enumerable starting at the max bound and continuing + * downward. + * + * @note If the type has no min bound, then this will be an infinite + * list. + */ + def enumFromMax(implicit A: UpperBounded[A]): LazyListLike.T[A] = + partialPrevious(A.maxBound).fold( + LazyListLike(A.maxBound) + )(prev => + enumFromThen(A.maxBound, prev) + ) +} + +object Enumerable { + def apply[A](implicit A: Enumerable[A]): Enumerable[A] = A + + def reverse[A](A: Enumerable[A]): Enumerable[A] = + new Enumerable[A] { + override def fromEnum(a: A): BigInt = + -A.fromEnum(a) + + override def toEnumOpt(i: BigInt): Option[A] = + A.toEnumOpt(-i) + + override val order: Order[A] = + Order.reverse(A.order) + + override def partialNext(a: A): Option[A] = + A.partialPrevious(a) + + override def partialPrevious(a: A): Option[A] = + A.partialNext(a) + } + + implicit def catsKernelEnumerableForBigInt: Enumerable[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt + implicit def catsKernelEnumerableForInt: Enumerable[Int] = + cats.kernel.instances.int.catsKernelStdBoundableEnumerableForInt + implicit def catsKernelEnumerableForUnit: Enumerable[Unit] = + cats.kernel.instances.unit.catsKernelStdBoundableEnumerableForUnit + implicit def catsKernelEnumerableForBoolean: Enumerable[Boolean] = + cats.kernel.instances.boolean.catsKernelStdBoundableEnumerableForBoolean + implicit def catsKernelEnumerableForByte: Enumerable[Byte] = + cats.kernel.instances.byte.catsKernelStdBoundableEnumerableForByte + implicit def catsKernelEnumerableForShort: Enumerable[Short] = + cats.kernel.instances.short.catsKernelStdBoundableEnumerableForShort + implicit def catsKernelEnumerableForLong: Enumerable[Long] = + cats.kernel.instances.long.catsKernelStdBoundableEnumerableForLong + implicit def catsKernelEnumerableForChar: Enumerable[Char] = + cats.kernel.instances.char.catsKernelStdBoundableEnumerableForChar +} /** * A typeclass with an operation which returns a member which is @@ -31,6 +295,121 @@ import scala.{specialized => sp} trait PartialNext[@sp A] { def partialOrder: PartialOrder[A] def partialNext(a: A): Option[A] + + /** As [[#partialNext]], but rather than getting the next element, it gets the + * Nth next element. + */ + def partialNextBy(a: A, n: BigInt): Option[A] = { + val Zero: BigInt = BigInt(0) + val One: BigInt= BigInt(1) + + @tailrec + def loop(acc: A, n: BigInt): Option[A] = + if (n <= Zero) { + Some(acc) + } else { + partialNext(acc) match { + case Some(acc) => + loop(acc, n - One) + case otherwise => + otherwise + } + } + + loop(a, n) + } + + /** As [[#nextOrMin]], but steps forward N steps rather than 1 step. + * + * @note If this wraps around to the `minBound`, and there are still steps + * to apply, it will not stop at `minBound`. For example, + * `nextOrMinBy(Byte.MaxValue, 2) == -127`. + */ + def nextOrMinBy(a: A, n: BigInt)(implicit A: LowerBounded[A]): A = { + val Zero: BigInt = BigInt(0) + val One: BigInt= BigInt(1) + + @tailrec + def loop(acc: A, n: BigInt): A = + if (n <= Zero) { + acc + } else { + partialNext(acc) match { + case Some(acc) => + loop(acc, n - One) + case _ => + loop(A.minBound, n - One) + } + } + + loop(a, n) + } + + /** Get the next value if defined, otherwise get the minBound. */ + def nextOrMin(a: A)(implicit A: LowerBounded[A]): A = + nextOrMinBy(a, BigInt(1)) + + /** Create an infinite cycling lazy list starting from the given value with + * each subsequent value N steps ahead of the last. When there is no next + * value, e.g. `partialNext` returns `None`, restart the cycle from the + * minBound. + */ + def cycleForwardFromBy(start: A, n: BigInt)(implicit A: LowerBounded[A]): LazyListLike.T[A] = + start #:: cycleForwardFromBy(nextOrMinBy(start, n), n) + + /** Create an infinite cycling lazy list starting from the given value. When + * there is no next value, e.g. `partialNext` returns `None`, restart the + * cycle from the minBound. + * + * @note This will only enumerate all the elements of the set if this type + * is a [[BoundableEnumerable]]. If the type is a + * [[BoundlessEnumerable]] then the cycle will never restart. If the + * type is neither, then it is possible the first cycle will + * enumerate elements that are not in following cycles. That is, if + * `nextOrMin` starting from `minBound` yields `None` before reaching + * `start`, `start` and elements following `start` will only be in + * the first cycle. This is not possible for [[BoundableEnumerable]] + * or [[BoundlessEnumerable]] types, but may be possible for types + * which only have a [[PartialNext]] instance. + */ + def cycleForwardFrom(start: A)(implicit A: LowerBounded[A]): LazyListLike.T[A] = + cycleForwardFromBy(start, BigInt(1)) + + /** As [[#cycleForwardFromByN]], but uses the minBound as the start value. */ + def cycleForwardBy(n: BigInt)(implicit A: LowerBounded[A]): LazyListLike.T[A] = + cycleForwardFromBy(A.minBound, n) + + /** As [[#cycleForwardFrom]], but uses the minBound as the start value. + * + * Because this cycle starts at the minBound, each cycle will have the same + * elements in it. However, as with [[#cycleForwardFrom]], each cycle will + * only contain all elements of `A` if `A` is either a + * [[BoundableEnumerable]] or [[BoundlessEnumerable]] type. + */ + def cycleForward(implicit A: LowerBounded[A]): LazyListLike.T[A] = + cycleForwardFrom(A.minBound) +} + +object PartialNext { + + def apply[A](implicit A: PartialNext[A]): PartialNext[A] = A + + implicit def catsKernelPartialNextForBigInt: Enumerable[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt + implicit def catsKernelPartialNextForInt: Enumerable[Int] = + cats.kernel.instances.int.catsKernelStdBoundableEnumerableForInt + implicit def catsKernelPartialNextForUnit: Enumerable[Unit] = + cats.kernel.instances.unit.catsKernelStdBoundableEnumerableForUnit + implicit def catsKernelPartialNextForBoolean: Enumerable[Boolean] = + cats.kernel.instances.boolean.catsKernelStdBoundableEnumerableForBoolean + implicit def catsKernelPartialNextForByte: Enumerable[Byte] = + cats.kernel.instances.byte.catsKernelStdBoundableEnumerableForByte + implicit def catsKernelPartialNextForShort: Enumerable[Short] = + cats.kernel.instances.short.catsKernelStdBoundableEnumerableForShort + implicit def catsKernelPartialNextForLong: Enumerable[Long] = + cats.kernel.instances.long.catsKernelStdBoundableEnumerableForLong + implicit def catsKernelPartialNextForChar: Enumerable[Char] = + cats.kernel.instances.char.catsKernelStdBoundableEnumerableForChar } /** @@ -42,15 +421,28 @@ trait Next[@sp A] extends PartialNext[A] { override def partialNext(a: A): Option[A] = Some(next(a)) } -/** - * A typeclass with an operation which returns a member which is - * smaller or `None` than the one supplied. - */ -trait PartialPrevious[@sp A] { - def partialOrder: PartialOrder[A] - def partialPrevious(a: A): Option[A] +object Next { + def apply[A](implicit A: Next[A]): Next[A] = A + + implicit def catsKernelNextForBigInt: Enumerable[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt + implicit def catsKernelNextForInt: Enumerable[Int] = + cats.kernel.instances.int.catsKernelStdBoundableEnumerableForInt + implicit def catsKernelNextForUnit: Enumerable[Unit] = + cats.kernel.instances.unit.catsKernelStdBoundableEnumerableForUnit + implicit def catsKernelNextForBoolean: Enumerable[Boolean] = + cats.kernel.instances.boolean.catsKernelStdBoundableEnumerableForBoolean + implicit def catsKernelNextForByte: Enumerable[Byte] = + cats.kernel.instances.byte.catsKernelStdBoundableEnumerableForByte + implicit def catsKernelNextForShort: Enumerable[Short] = + cats.kernel.instances.short.catsKernelStdBoundableEnumerableForShort + implicit def catsKernelNextForLong: Enumerable[Long] = + cats.kernel.instances.long.catsKernelStdBoundableEnumerableForLong + implicit def catsKernelNextForChar: Enumerable[Char] = + cats.kernel.instances.char.catsKernelStdBoundableEnumerableForChar } + /** * A typeclass with an operation which returns a member which is * always smaller than the one supplied. @@ -58,6 +450,7 @@ trait PartialPrevious[@sp A] { trait Previous[@sp A] extends PartialPrevious[A] { def partialOrder: PartialOrder[A] def previous(a: A): A + override def partialPrevious(a: A): Option[A] = Some(previous(a)) } @@ -65,24 +458,44 @@ trait Previous[@sp A] extends PartialPrevious[A] { * A typeclass which has both `previous` and `next` operations * such that `next . previous == identity`. */ +// TODO: Not sure what to do about UnboundedEnumerable. It should extend +// Enumerable, but we can't do that without breaking +// bincompat. BoundlessEnumerable could extend UnboundedEnumerable, but that +// seems silly... trait UnboundedEnumerable[@sp A] extends Next[A] with Previous[A] { def order: Order[A] override def partialOrder: PartialOrder[A] = order } +trait BoundlessEnumerable[@sp A] extends Enumerable[A] with Next[A] with Previous[A] { + def toEnum(i: BigInt): A + + override final def toEnumOpt(i: BigInt): Option[A] = Some(toEnum(i)) +} + +object BoundlessEnumerable { + def apply[A](implicit A: BoundlessEnumerable[A]): BoundlessEnumerable[A] = A + + implicit def catsKernelBoundlessEnumerableForInt: BoundlessEnumerable[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt +} + +@deprecated(message = "Please use BoundableEnumerable instead.", since = "2.10.0") trait BoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with PartialNextLowerBounded[A] { def order: Order[A] override def partialOrder: PartialOrder[A] = order + @deprecated(message = "Please use nextOrMin.", since = "2.10.0") def cycleNext(a: A): A = partialNext(a).getOrElse(minBound) + @deprecated(message = "Please use previousOrMax instead.", since = "2.10.0") def cyclePrevious(a: A): A = partialPrevious(a).getOrElse(maxBound) - } +@deprecated(message = "Please use BoundableEnumerable instead.", since = "2.10.0") object BoundedEnumerable { implicit def catsKernelBoundedEnumerableForUnit: BoundedEnumerable[Unit] = cats.kernel.instances.unit.catsKernelStdOrderForUnit @@ -117,11 +530,33 @@ object BoundedEnumerable { } } +trait BoundableEnumerable[@sp A] extends Enumerable[A] with UpperBounded[A] with LowerBounded[A] { + + // If [[#fromEnum]] is defined in such a way that the elements of this set + // map to elements of the set of integers ''in the same order'', then this + // is can be defined as `fromEnum(maxBound) - fromEnum(maxBound) + + // BigInt(1)`, if and only if for all members of this set, `fromEnum(x) < + // fromEnum(y) => x < y`. Or in other words, this set can be ordered using + // `Order[BigInt]` and this set's mapping to `BigInt`. This property is + // likely to hold for most instances of this type, though it is not required + // to hold for the type to be a lawful instance. Indeed, an `Inverse[A]` + // type, which flips the ordering of the underlying type would not hold to + // this property for the simple derived implementation. + /** The number of elements in the set defined by this enumerable. */ + def size: BigInt +} + +object BoundableEnumerable { + def apply[A](implicit A: BoundableEnumerable[A]): BoundableEnumerable[A] = A +} + +@deprecated(message = "Please use Enumerable instead.", since = "2.10.0") trait LowerBoundedEnumerable[@sp A] extends PartialNextLowerBounded[A] with Next[A] { def order: Order[A] override def partialOrder: PartialOrder[A] = order } +@deprecated(message = "Please use Enumerable instead.", since = "2.10.0") trait UpperBoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with Previous[A] { def order: Order[A] override def partialOrder: PartialOrder[A] = order diff --git a/kernel/src/main/scala/cats/kernel/LazyListCompat.scala b/kernel/src/main/scala/cats/kernel/LazyListCompat.scala new file mode 100644 index 0000000000..54848167ad --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/LazyListCompat.scala @@ -0,0 +1,10 @@ +package cats +package kernel + +private[kernel] trait LazyListCompatBase{ + type T[A] + + def apply[A](a: A*): T[A] + + final def empty[A]: T[A] = apply[A]() +} diff --git a/kernel/src/main/scala/cats/kernel/PartialForward.scala b/kernel/src/main/scala/cats/kernel/PartialForward.scala new file mode 100644 index 0000000000..98ab8359d7 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/PartialForward.scala @@ -0,0 +1,60 @@ +package cats +package kernel + +import scala.{specialized => sp} +import scala.annotation.tailrec + +trait PartialForward[@sp A] extends Serializable { + def partialOrder: PartialOrder[A] + + def partialNext(a: A): Option[A] + + /** As [[#partialNext]], but rather than getting the next element, it gets the + * Nth next element. + */ + def partialNextBy(a: A, n: BigInt): Option[A] = { + val Zero: BigInt = BigInt(0) + val One: BigInt= BigInt(1) + + @tailrec + def loop(acc: A, n: BigInt): Option[A] = + if (n <= Zero) { + Some(acc) + } else { + partialNext(acc) match { + case Some(acc) => + loop(acc, n - One) + case otherwise => + otherwise + } + } + + loop(a, n) + } + + /** As [[#nextOrMin]], but steps forward N steps rather than 1 step. + * + * @note If this wraps around to the `minBound`, and there are still steps + * to apply, it will not stop at `minBound`. For example, + * `nextOrMinBy(Byte.MaxValue, 2) == -127`. + */ + def nextOrMinBy(a: A, n: BigInt)(implicit A: LowerBounded[A]): A = { + val Zero: BigInt = BigInt(0) + val One: BigInt= BigInt(1) + + @tailrec + def loop(acc: A, n: BigInt): A = + if (n <= Zero) { + acc + } else { + partialNext(acc) match { + case Some(acc) => + loop(acc, n - One) + case _ => + loop(A.minBound, n - One) + } + } + + loop(a, n) + } +} diff --git a/kernel/src/main/scala/cats/kernel/PartialPrevious.scala b/kernel/src/main/scala/cats/kernel/PartialPrevious.scala new file mode 100644 index 0000000000..2b495524ca --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/PartialPrevious.scala @@ -0,0 +1,67 @@ +package cats +package kernel + +import scala.annotation.tailrec +import scala.{specialized => sp} + +/** + * A typeclass with an operation which returns a member which is + * smaller or `None` than the one supplied. + */ +trait PartialPrevious[@sp A] { + def partialOrder: PartialOrder[A] + def partialPrevious(a: A): Option[A] + + /** As [[#partialPrevious]], but rather than getting the previous element, it gets the + * Nth previous element. + */ + def partialPreviousBy(a: A, n: BigInt): Option[A] = { + val Zero: BigInt = BigInt(0) + val One: BigInt = BigInt(1) + + @tailrec + def loop(acc: A, n: BigInt): Option[A] = + if (n <= Zero) { + Some(acc) + } else { + partialPrevious(acc) match { + case Some(acc) => + loop(acc, n - One) + case otherwise => + otherwise + } + } + + loop(a, n) + } +} + +object PartialPrevious { + + def apply[A](implicit A: PartialPrevious[A]): PartialPrevious[A] = A + + def reverse[A](partialPrevious: PartialPrevious[A]): PartialNext[A] = + new PartialNext[A] { + override val partialOrder: PartialOrder[A] = + PartialOrder.reverse(partialPrevious.partialOrder) + override def partialNext(a: A): Option[A] = + partialPrevious.partialPrevious(a) + } + + implicit def catsKernelPartialPreviousForBigInt: PartialPrevious[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt + implicit def catsKernelPartialPreviousForInt: PartialPrevious[Int] = + cats.kernel.instances.int.catsKernelStdBoundableEnumerableForInt + implicit def catsKernelPartialPreviousForUnit: PartialPrevious[Unit] = + cats.kernel.instances.unit.catsKernelStdBoundableEnumerableForUnit + implicit def catsKernelPartialPreviousForBoolean: PartialPrevious[Boolean] = + cats.kernel.instances.boolean.catsKernelStdBoundableEnumerableForBoolean + implicit def catsKernelPartialPreviousForByte: PartialPrevious[Byte] = + cats.kernel.instances.byte.catsKernelStdBoundableEnumerableForByte + implicit def catsKernelPartialPreviousForShort: PartialPrevious[Short] = + cats.kernel.instances.short.catsKernelStdBoundableEnumerableForShort + implicit def catsKernelPartialPreviousForLong: PartialPrevious[Long] = + cats.kernel.instances.long.catsKernelStdBoundableEnumerableForLong + implicit def catsKernelPartialPreviousForChar: PartialPrevious[Char] = + cats.kernel.instances.char.catsKernelStdBoundableEnumerableForChar +} diff --git a/kernel/src/main/scala/cats/kernel/UpperBoundedPartialPrevious.scala b/kernel/src/main/scala/cats/kernel/UpperBoundedPartialPrevious.scala new file mode 100644 index 0000000000..bce50ce4c3 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/UpperBoundedPartialPrevious.scala @@ -0,0 +1,117 @@ +package cats +package kernel + +import cats.kernel.{ScalaVersionSpecificLazyListCompat => LazyListLike} +import scala.annotation.tailrec +import scala.{specialized => sp} + +trait UpperBoundedPartialPrevious[@sp A] extends PartialPrevious[A] with UpperBounded[A] { + // Note this class provides no abstract methods. In fact, an argument can be + // made that it need not exist at all, and that these methods should live on + // PartialPrevious with a constraint at A has an UpperBounded[A] + // instance. The reason that this class exists is because the Order family + // of classes define a function on their companion object called `reverse`, + // which changes the ordering and direction of all operations. For this to + // be consistent, we need a class which extends both PartialPrevious and + // UpperBounded so that when we reverse PartialPrevious, we also reverse + // UpperBounded. + + /** As [[#previousOrMax]], but steps backward N steps rather than 1 step. + * + * @note If this wraps around to the `maxBound`, and there are still steps + * to apply, it will not stop at `maxBound`. For example, + * `previousOrMaxBy(Byte.MinValue, 2) == 126`. + */ + def previousOrMaxBy(a: A, n: BigInt): A = { + val Zero: BigInt = BigInt(0) + val One: BigInt= BigInt(1) + + @tailrec + def loop(acc: A, n: BigInt): A = + if (n <= Zero) { + acc + } else { + partialPrevious(acc) match { + case Some(acc) => + loop(acc, n - One) + case _ => + loop(maxBound, n - One) + } + } + + loop(a, n) + } + + /** Get the previous value if defined, otherwise get the maxBound. */ + def previousOrMax(a: A): A = + previousOrMaxBy(a, BigInt(1)) + + /** Create an infinite cycling lazy list starting from the given value with + * each subsequent value N steps behind of the last. When there is no previous + * value, e.g. `partialPrevious` returns `None`, restart the cycle from the + * maxBound. + */ + def cycleBackwardFromBy(start: A, n: BigInt): LazyListLike.T[A] = + start #:: cycleBackwardFromBy(previousOrMaxBy(start, n), n) + + /** Create an infinite cycling lazy list starting from the given value. When + * there is no previous value, e.g. `partialPrevious` returns `None`, restart the + * cycle from the maxBound. + * + * @note This will only enumerate all the elements of the set if this type + * is a [[BoundableEnumerable]]. If the type is a + * [[BoundlessEnumerable]] then the cycle will never restart. If the + * type is neither, then it is possible the first cycle will + * enumerate elements that are not in following cycles. That is, if + * `previousOrMin` starting from `maxBound` yields `None` before reaching + * `start`, `start` and elements following `start` will only be in + * the first cycle. This is not possible for [[BoundableEnumerable]] + * or [[BoundlessEnumerable]] types, but may be possible for types + * which only have a [[PartialPrevious]] instance. + */ + def cycleBackwardFrom(start: A): LazyListLike.T[A] = + cycleBackwardFromBy(start, BigInt(1)) + + /** As [[#cycleBackwardFromByN]], but uses the maxBound as the start value. */ + def cycleBackwardBy(n: BigInt): LazyListLike.T[A] = + cycleBackwardFromBy(maxBound, n) + + /** As [[#cycleBackwardFrom]], but uses the maxBound as the start value. + * + * Because this cycle starts at the maxBound, each cycle will have the same + * elements in it. However, as with [[#cycleBackwardFrom]], each cycle will + * only contain all elements of `A` if `A` is either a + * [[BoundableEnumerable]] or [[BoundlessEnumerable]] type. + */ + def cycleBackward: LazyListLike.T[A] = + cycleBackwardFrom(maxBound) +} + +object UpperBoundedPartialPrevious { + def apply[A](implicit A: UpperBoundedPartialPrevious[A]): UpperBoundedPartialPrevious[A] = A + + def reverse[A](partialPrevious: UpperBoundedPartialPrevious[A]): LowerBoundedPartialNext[A] = + new PartialNext[A] { + override val partialOrder: PartialOrder[A] = + PartialOrder.reverse(partialPrevious.partialOrder) + override def partialNext(a: A): Option[A] = + partialPrevious.partialPrevious(a) + } + + implicit def catsKernelUpperBoundedPartialPreviousForBigInt: UpperBoundedPartialPrevious[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt + implicit def catsKernelUpperBoundedPartialPreviousForInt: UpperBoundedPartialPrevious[Int] = + cats.kernel.instances.int.catsKernelStdBoundableEnumerableForInt + implicit def catsKernelUpperBoundedPartialPreviousForUnit: UpperBoundedPartialPrevious[Unit] = + cats.kernel.instances.unit.catsKernelStdBoundableEnumerableForUnit + implicit def catsKernelUpperBoundedPartialPreviousForBoolean: UpperBoundedPartialPrevious[Boolean] = + cats.kernel.instances.boolean.catsKernelStdBoundableEnumerableForBoolean + implicit def catsKernelUpperBoundedPartialPreviousForByte: UpperBoundedPartialPrevious[Byte] = + cats.kernel.instances.byte.catsKernelStdBoundableEnumerableForByte + implicit def catsKernelUpperBoundedPartialPreviousForShort: UpperBoundedPartialPrevious[Short] = + cats.kernel.instances.short.catsKernelStdBoundableEnumerableForShort + implicit def catsKernelUpperBoundedPartialPreviousForLong: UpperBoundedPartialPrevious[Long] = + cats.kernel.instances.long.catsKernelStdBoundableEnumerableForLong + implicit def catsKernelUpperBoundedPartialPreviousForChar: UpperBoundedPartialPrevious[Char] = + cats.kernel.instances.char.catsKernelStdBoundableEnumerableForChar +} diff --git a/kernel/src/main/scala/cats/kernel/instances/BigIntInstances.scala b/kernel/src/main/scala/cats/kernel/instances/BigIntInstances.scala index 83a7360959..e741757ffc 100644 --- a/kernel/src/main/scala/cats/kernel/instances/BigIntInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/BigIntInstances.scala @@ -23,7 +23,7 @@ package cats.kernel package instances trait BigIntInstances { - implicit val catsKernelStdOrderForBigInt: Order[BigInt] with Hash[BigInt] with UnboundedEnumerable[BigInt] = + implicit val catsKernelStdOrderForBigInt: Order[BigInt] with Hash[BigInt] with BoundlessEnumerable[BigInt] = new BigIntOrder implicit val catsKernelStdGroupForBigInt: CommutativeGroup[BigInt] = new BigIntGroup @@ -36,12 +36,17 @@ class BigIntGroup extends CommutativeGroup[BigInt] { override def remove(x: BigInt, y: BigInt): BigInt = x - y } +trait BigIntBoundlessEnumerable extends BoundlessEnumerable[BigInt] { + override final def toEnum(i: BigInt): BigInt = i + override final def fromEnum(i: BigInt): BigInt = i +} + trait BigIntUnboundedEnum extends UnboundedEnumerable[BigInt] { override def next(a: BigInt): BigInt = a + 1 override def previous(a: BigInt): BigInt = a - 1 } -class BigIntOrder extends Order[BigInt] with Hash[BigInt] with BigIntUnboundedEnum { +class BigIntOrder extends Order[BigInt] with Hash[BigInt] with BigIntUnboundedEnum with BigIntBoundlessEnumerable { def hash(x: BigInt): Int = x.hashCode() def compare(x: BigInt, y: BigInt): Int = x.compare(y) diff --git a/kernel/src/main/scala/cats/kernel/instances/BooleanInstances.scala b/kernel/src/main/scala/cats/kernel/instances/BooleanInstances.scala index ad818d478d..dafbf32814 100644 --- a/kernel/src/main/scala/cats/kernel/instances/BooleanInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/BooleanInstances.scala @@ -22,11 +22,18 @@ package cats.kernel package instances +import scala.annotation.nowarn + trait BooleanInstances { - implicit val catsKernelStdOrderForBoolean: Order[Boolean] with Hash[Boolean] with BoundedEnumerable[Boolean] = + implicit val catsKernelStdBoundableEnumerableForBoolean: Order[Boolean] with Hash[Boolean] with BoundableEnumerable[Boolean] = + new BooleanOrder + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForBoolean", since = "2.10.0") + val catsKernelStdOrderForBoolean: Order[Boolean] with Hash[Boolean] with BoundedEnumerable[Boolean] = new BooleanOrder } +@deprecated(message = "Please use BooleanBoundableEnumerable.", since = "2.10.0") trait BooleanEnumerable extends BoundedEnumerable[Boolean] { override def partialNext(a: Boolean): Option[Boolean] = if (!a) Some(true) else None @@ -34,12 +41,33 @@ trait BooleanEnumerable extends BoundedEnumerable[Boolean] { if (a) Some(false) else None } +private[instances] trait BooleanBoundableEnumerable extends BoundableEnumerable[Boolean] { + override final val size: BigInt = BigInt(2) + + override final def fromEnum(a: Boolean): BigInt = + if (a) { + BigInt(1) + } else { + BigInt(0) + } + + override final def toEnumOpt(i: BigInt): Option[Boolean] = + if (i == BigInt(0)) { + Some(false) + } else if (i == BigInt(1)) { + Some(true) + } else { + None + } +} + trait BooleanBounded extends LowerBounded[Boolean] with UpperBounded[Boolean] { override def minBound: Boolean = false override def maxBound: Boolean = true } -class BooleanOrder extends Order[Boolean] with Hash[Boolean] with BooleanBounded with BooleanEnumerable { self => +@nowarn("msg=BooleanBoundableEnumerable") +class BooleanOrder extends Order[Boolean] with Hash[Boolean] with BooleanBounded with BooleanEnumerable with BooleanBoundableEnumerable { self => def hash(x: Boolean): Int = x.hashCode() def compare(x: Boolean, y: Boolean): Int = diff --git a/kernel/src/main/scala/cats/kernel/instances/ByteInstances.scala b/kernel/src/main/scala/cats/kernel/instances/ByteInstances.scala index 8d767997ef..a0f5232f02 100644 --- a/kernel/src/main/scala/cats/kernel/instances/ByteInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/ByteInstances.scala @@ -22,10 +22,16 @@ package cats.kernel package instances +import scala.annotation.nowarn + trait ByteInstances { - implicit val catsKernelStdOrderForByte: Order[Byte] with Hash[Byte] with BoundedEnumerable[Byte] = + implicit val catsKernelStdBoundableEnumerableForByte: Order[Byte] with Hash[Byte] with BoundableEnumerable[Byte] = new ByteOrder implicit val catsKernelStdGroupForByte: CommutativeGroup[Byte] = new ByteGroup + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForByte", since = "2.10.0") + val catsKernelStdOrderForByte: Order[Byte] with Hash[Byte] with BoundedEnumerable[Byte] = + new ByteOrder } class ByteGroup extends CommutativeGroup[Byte] { @@ -35,6 +41,7 @@ class ByteGroup extends CommutativeGroup[Byte] { override def remove(x: Byte, y: Byte): Byte = (x - y).toByte } +@deprecated(message = "Please use ByteBoundableEnumerable.", since = "2.10.0") trait ByteEnumerable extends BoundedEnumerable[Byte] { override def partialNext(a: Byte): Option[Byte] = if (order.eqv(a, maxBound)) None else Some((a + 1).toByte) @@ -42,12 +49,28 @@ trait ByteEnumerable extends BoundedEnumerable[Byte] { if (order.eqv(a, minBound)) None else Some((a - 1).toByte) } +private[instances] trait ByteBoundableEnumerable extends BoundableEnumerable[Byte] { + override final def size: BigInt = + (BigInt(minBound.toInt) - BigInt(maxBound.toInt)) + BigInt(1) + + override final def fromEnum(a: Byte): BigInt = + BigInt(a.toInt) + + override final def toEnumOpt(i: BigInt): Option[Byte] = + if (i >= BigInt(minBound.toInt) && i <= BigInt(maxBound.toInt)) { + Some(i.toByte) + } else { + None + } +} + trait ByteBounded extends LowerBounded[Byte] with UpperBounded[Byte] { override def minBound: Byte = Byte.MinValue override def maxBound: Byte = Byte.MaxValue } -class ByteOrder extends Order[Byte] with Hash[Byte] with ByteBounded with ByteEnumerable { self => +@nowarn("msg=ByteBoundableEnumerable") +class ByteOrder extends Order[Byte] with Hash[Byte] with ByteBounded with ByteEnumerable with ByteBoundableEnumerable { self => def hash(x: Byte): Int = x.hashCode() diff --git a/kernel/src/main/scala/cats/kernel/instances/CharInstances.scala b/kernel/src/main/scala/cats/kernel/instances/CharInstances.scala index 2ff7b77f08..af9fa298c5 100644 --- a/kernel/src/main/scala/cats/kernel/instances/CharInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/CharInstances.scala @@ -22,10 +22,18 @@ package cats.kernel package instances +import scala.annotation.nowarn + trait CharInstances { - implicit val catsKernelStdOrderForChar: CharOrder with Hash[Char] with BoundedEnumerable[Char] = new CharOrder + implicit val catsKernelStdBoundableEnumerableForChar: CharOrder with Hash[Char] with BoundableEnumerable[Char] = + new CharOrder + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForChar instead.", since = "2.10.0") + def catsKernelStdOrderForChar: CharOrder with Hash[Char] with BoundedEnumerable[Char] = + catsKernelStdBoundableEnumerableForChar } +@deprecated(message = "Please use CharBoundableEnumerable.", since = "2.10.0") trait CharEnumerable extends BoundedEnumerable[Char] { override def partialNext(a: Char): Option[Char] = if (a == maxBound) None else Some((a + 1).toChar) @@ -33,12 +41,28 @@ trait CharEnumerable extends BoundedEnumerable[Char] { if (a == minBound) None else Some((a - 1).toChar) } +private[instances] trait CharBoundableEnumerable extends BoundableEnumerable[Char] { + override final def size: BigInt = + BigInt(maxBound.toInt) + + override final def fromEnum(a: Char): BigInt = + BigInt(a.toInt) + + override final def toEnumOpt(i: BigInt): Option[Char] = + if (i >= BigInt(minBound.toInt) && i <= BigInt(maxBound.toInt)) { + Some(i.toChar) + } else { + None + } +} + trait CharBounded extends LowerBounded[Char] with UpperBounded[Char] { override def minBound: Char = Char.MinValue override def maxBound: Char = Char.MaxValue } -class CharOrder extends Order[Char] with Hash[Char] with CharBounded with CharEnumerable { self => +@nowarn("msg=CharBoundableEnumerable") +class CharOrder extends Order[Char] with Hash[Char] with CharBounded with CharEnumerable with CharBoundableEnumerable { self => def hash(x: Char): Int = x.hashCode() def compare(x: Char, y: Char): Int = if (x < y) -1 else if (x > y) 1 else 0 diff --git a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala index a0c3b5366e..653305c4be 100644 --- a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala @@ -22,10 +22,17 @@ package cats.kernel package instances +import scala.annotation.nowarn + trait IntInstances { - implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with BoundedEnumerable[Int] = + implicit val catsKernelStdBoundableEnumerableForInt: Order[Int] with Hash[Int] with BoundableEnumerable[Int] = new IntOrder + implicit val catsKernelStdGroupForInt: CommutativeGroup[Int] = new IntGroup + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForInt instead.", since = "2.10.0") + val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with BoundedEnumerable[Int] with Enumerable[Int] = + new IntOrder } class IntGroup extends CommutativeGroup[Int] { @@ -35,6 +42,7 @@ class IntGroup extends CommutativeGroup[Int] { override def remove(x: Int, y: Int): Int = x - y } +@deprecated(message = "Please use IntBoundableEnumerable.", since = "2.10.0") trait IntEnumerable extends BoundedEnumerable[Int] { override def partialNext(a: Int): Option[Int] = if (order.eqv(a, maxBound)) None else Some(a + 1) @@ -42,12 +50,27 @@ trait IntEnumerable extends BoundedEnumerable[Int] { if (order.eqv(a, minBound)) None else Some(a - 1) } +private[instances] trait IntBoundableEnumerable extends BoundableEnumerable[Int] { + override final def size: BigInt = + (BigInt(maxBound) - BigInt(minBound)) + BigInt(1) + + override final def fromEnum(a: Int): BigInt = BigInt(a) + + override final def toEnumOpt(i: BigInt): Option[Int] = + if (i >= BigInt(minBound) && i <= BigInt(maxBound)) { + Some(i.toInt) + } else { + None + } +} + trait IntBounded extends LowerBounded[Int] with UpperBounded[Int] { override def minBound: Int = Int.MinValue override def maxBound: Int = Int.MaxValue } -class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumerable { self => +@nowarn("msg=IntBoundableEnumerable") +class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumerable with IntBoundableEnumerable { self => def hash(x: Int): Int = x.hashCode() def compare(x: Int, y: Int): Int = if (x < y) -1 else if (x > y) 1 else 0 diff --git a/kernel/src/main/scala/cats/kernel/instances/LongInstances.scala b/kernel/src/main/scala/cats/kernel/instances/LongInstances.scala index 8be97210c8..942e90a3fe 100644 --- a/kernel/src/main/scala/cats/kernel/instances/LongInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/LongInstances.scala @@ -22,10 +22,17 @@ package cats.kernel package instances +import scala.annotation.nowarn + trait LongInstances { - implicit val catsKernelStdOrderForLong: Order[Long] with Hash[Long] with BoundedEnumerable[Long] = + implicit val catsKernelStdBoundableEnumerableForLong: Order[Long] with Hash[Long] with BoundableEnumerable[Long] = new LongOrder + implicit val catsKernelStdGroupForLong: CommutativeGroup[Long] = new LongGroup + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForLong", since = "2.10.0") + val catsKernelStdOrderForLong: Order[Long] with Hash[Long] with BoundedEnumerable[Long] = + new LongOrder } class LongGroup extends CommutativeGroup[Long] { @@ -35,6 +42,7 @@ class LongGroup extends CommutativeGroup[Long] { override def remove(x: Long, y: Long): Long = x - y } +@deprecated(message = "Please use LongBoundableEnumerable.", since = "2.10.0") trait LongEnumerable extends BoundedEnumerable[Long] { override def partialNext(a: Long): Option[Long] = if (order.neqv(a, maxBound)) Some(a + 1L) else None @@ -42,12 +50,28 @@ trait LongEnumerable extends BoundedEnumerable[Long] { if (order.neqv(a, minBound)) Some(a - 1L) else None } +private[instances] trait LongBoundableEnumerable extends BoundableEnumerable[Long] { + override final def size: BigInt = + (BigInt(maxBound) - BigInt(minBound)) + BigInt(1) + + override final def fromEnum(a: Long): BigInt = + BigInt(a) + + override final def toEnumOpt(i: BigInt): Option[Long] = + if (i >= BigInt(minBound) && i <= BigInt(maxBound)) { + Some(i.toLong) + } else { + None + } +} + trait LongBounded extends UpperBounded[Long] with LowerBounded[Long] { override def minBound: Long = Long.MinValue override def maxBound: Long = Long.MaxValue } -class LongOrder extends Order[Long] with Hash[Long] with LongBounded with LongEnumerable { self => +@nowarn("msg=LongBoundableEnumerable") +class LongOrder extends Order[Long] with Hash[Long] with LongBounded with LongEnumerable with LongBoundableEnumerable { self => def hash(x: Long): Int = x.hashCode() diff --git a/kernel/src/main/scala/cats/kernel/instances/ShortInstances.scala b/kernel/src/main/scala/cats/kernel/instances/ShortInstances.scala index 070f5bc203..2e7558bd64 100644 --- a/kernel/src/main/scala/cats/kernel/instances/ShortInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/ShortInstances.scala @@ -22,9 +22,18 @@ package cats.kernel package instances +import scala.annotation.nowarn + trait ShortInstances { - implicit val catsKernelStdOrderForShort: Order[Short] with Hash[Short] with BoundedEnumerable[Short] = new ShortOrder + + implicit val catsKernelStdBoundableEnumerableForShort: Order[Short] with Hash[Short] with BoundableEnumerable[Short] = + new ShortOrder + implicit val catsKernelStdGroupForShort: CommutativeGroup[Short] = new ShortGroup + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForShort", since = "2.10.0") + val catsKernelStdOrderForShort: Order[Short] with Hash[Short] with BoundedEnumerable[Short] = + new ShortOrder } class ShortGroup extends CommutativeGroup[Short] { @@ -34,6 +43,7 @@ class ShortGroup extends CommutativeGroup[Short] { override def remove(x: Short, y: Short): Short = (x - y).toShort } +@deprecated(message = "Please use ShortBoundableEnumerable.", since = "2.10.0") trait ShortEnumerable extends BoundedEnumerable[Short] { override def partialNext(a: Short): Option[Short] = if (order.eqv(a, maxBound)) None else Some((a + 1).toShort) @@ -41,12 +51,28 @@ trait ShortEnumerable extends BoundedEnumerable[Short] { if (order.eqv(a, minBound)) None else Some((a - 1).toShort) } +private[instances] trait ShortBoundableEnumerable extends BoundableEnumerable[Short] { + override final val size: BigInt = + (BigInt(maxBound.toInt) - BigInt(minBound.toInt)) + BigInt(1) + + override final def fromEnum(a: Short): BigInt = + BigInt(a.toInt) + + override final def toEnumOpt(i: BigInt): Option[Short] = + if (i >= BigInt(minBound.toInt) && i <= BigInt(maxBound.toInt)) { + Some(i.toShort) + } else { + None + } +} + trait ShortBounded extends LowerBounded[Short] with UpperBounded[Short] { override def minBound: Short = Short.MinValue override def maxBound: Short = Short.MaxValue } -class ShortOrder extends Order[Short] with Hash[Short] with ShortBounded with ShortEnumerable { self => +@nowarn("msg=ShortBoundableEnumerable") +class ShortOrder extends Order[Short] with Hash[Short] with ShortBounded with ShortEnumerable with ShortBoundableEnumerable { self => def hash(x: Short): Int = x.hashCode() // use java.lang.Short.compare if we can rely on java >= 1.7 diff --git a/kernel/src/main/scala/cats/kernel/instances/UnitInstances.scala b/kernel/src/main/scala/cats/kernel/instances/UnitInstances.scala index b7b28974a6..932e320b46 100644 --- a/kernel/src/main/scala/cats/kernel/instances/UnitInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/UnitInstances.scala @@ -25,24 +25,40 @@ import compat.scalaVersionSpecific._ @suppressUnusedImportWarningForScalaVersionSpecific trait UnitInstances { - implicit val catsKernelStdOrderForUnit: Order[Unit] with Hash[Unit] with BoundedEnumerable[Unit] = + implicit val catsKernelStdBoundableEnumerableForUnit: Order[Unit] with Hash[Unit] with BoundableEnumerable[Unit] with BoundedEnumerable[Unit] = new UnitOrder implicit val catsKernelStdAlgebraForUnit: BoundedSemilattice[Unit] with CommutativeGroup[Unit] = new UnitAlgebra + + @deprecated(message = "Please use catsKernelStdBoundableEnumerableForUnit", since = "2.10.0") + def catsKernelStdOrderForUnit: Order[Unit] with Hash[Unit] with BoundedEnumerable[Unit] = + catsKernelStdBoundableEnumerableForUnit } +@deprecated(message = "Please use BoundableEnumerable.", since = "2.10.0") trait UnitEnumerable extends BoundedEnumerable[Unit] { override def partialNext(x: Unit): Option[Unit] = None override def partialPrevious(x: Unit): Option[Unit] = None } +private[instances] trait UnitBoundableEnumerable extends BoundableEnumerable[Unit] { + override final val size: BigInt = BigInt(0) + override final def fromEnum(a: Unit): BigInt = BigInt(0) + override final def toEnumOpt(i: BigInt): Option[Unit] = + if (i == BigInt(0)) { + Some(()) + } else { + None + } +} + trait UnitBounded extends LowerBounded[Unit] with UpperBounded[Unit] { override def minBound: Unit = () override def maxBound: Unit = () } -class UnitOrder extends Order[Unit] with Hash[Unit] with UnitBounded with UnitEnumerable { self => +class UnitOrder extends Order[Unit] with Hash[Unit] with UnitBounded with UnitEnumerable with UnitBoundableEnumerable { self => def compare(x: Unit, y: Unit): Int = 0 def hash(x: Unit): Int = 0 // ().hashCode() == 0