From 9debe1c1f439a66f5792d3349471f98fd953af51 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Wed, 16 Nov 2022 16:51:47 -0700 Subject: [PATCH 01/11] poc --- .../main/scala/cats/kernel/Enumerable.scala | 195 ++++++++++++++++++ .../cats/kernel/instances/IntInstances.scala | 12 +- 2 files changed, 205 insertions(+), 2 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index d287113d27..25b4d50de4 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -22,8 +22,163 @@ package cats package kernel +import scala.collection.immutable.LazyList +import scala.annotation.tailrec import scala.{specialized => sp} +/** A typeclass for types which are countable. Formally this means that values + * can be mapped on to the natural numbers. + * + * Because Countable types may be mapped to the natural numbers, being an + * instance of `Countable` 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`. + * + * @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 Countable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ + def order: Order[A] + def fromEnum(a: A): BigInt + def toEnum(i: BigInt): Option[A] + + /** The fundamental function in the `Countable` class. Given a start position, + * an offset, and an optional last position, enumerate the values between + * `first` and `last` (or `MaxValue` or infinity), using a step of `by - + * first`. + * + * {{{ + * scala> Countable[Int].enumFromThenToOpt(1, 3, Some(11)).toList + * val res0: List[Int] = List(1, 3, 5, 7, 9, 11) + * }}} + * + * All other enum like functions can be expressed in terms of this + * function. + */ + def enumFromThenToOpt(first: A, by: A, last: Option[A]): LazyList[A] = { + val Zero: BigInt = BigInt(0) + val increment: BigInt = fromEnum(by) - fromEnum(first) + + def loop(i: A): LazyList[A] = + if (increment > Zero) { + // forwards + partialNextByN(i, increment) match { + case Some(next) => + if (last.fold(false)(order.gt(next, _))) { + LazyList.empty[A] + } else { + next #:: loop(next) + } + case _ => + LazyList.empty[A] + } + } else { + // backwards or zero + partialPreviousByN(i, increment.abs) match { + case Some(next) => + if (last.fold(false)(order.lt(next, _))) { + LazyList.empty + } else { + next #:: loop(next) + } + case _ => + LazyList.empty + } + } + + last match { + case Some(last) => + order.compare(first, last) match { + case result if result < Zero => + if (increment < Zero) { + LazyList.empty[A] + } else { + first #:: loop(first) + } + case result if result > Zero => + if (increment > Zero) { + LazyList.empty[A] + } else { + first #:: loop(first) + } + case _ => + first #:: loop(first) + } + case _ => + first #:: loop(first) + } + } + + /** Given a start position, an offset, and a last position, enumerate the + * values between `first` and `last`, using a step of `by - first`. + * + * {{{ + * scala> Countable[Int].enumFromThenTo(1, 3, 11).toList + * val res0: List[Int] = List(1, 3, 5, 7, 9, 11) + * }}} + */ + def enumFromThenTo(first: A, by: A, last: A): LazyList[A] = + enumFromThenToOpt(first, by, Some(last)) + + /** Given a start position and a last position, enumerate the + * values between `first` and `last`, using a step of 1. + * + * {{{ + * scala> Countable[Int].enumFromTo(1, 5).toList + * val res0: List[Int] = List(1, 2, 3, 4, 5) + * }}} + */ + def enumFromTo(first: A, last: A): LazyList[A] = + partialNext(first) match { + case Some(by) => + enumFromThenTo(first, by, last) + case _ => + if (order.lteqv(first, last)) { + LazyList(first) + } else { + LazyList.empty + } + } + + /** Given a start position and a increment, enumerate the values starting at + * `first` until `MaxValue` or infinity if the type is unbounded. + * + * {{{ + * scala> Countable[Int].enumFromThen(Int.MaxValue - 5, Int.MaxValue - 4).toList + * val res0: List[Int] = List(2147483642, 2147483643, 2147483644, 2147483645, 2147483646, 2147483647) + * }}} + */ + def enumFromThen(first: A, by: A): LazyList[A] = + enumFromThenToOpt(first, by, None) + + /** Given a start position, enumerate the values starting at `first` by 1, + * until `MaxValue` or infinity if the type is unbounded. + * + * {{{ + * scala> Countable[Int].enumFrom(Int.MaxValue - 5).toList + * val res0: List[Int] = List(2147483642, 2147483643, 2147483644, 2147483645, 2147483646, 2147483647) + * }}} + */ + def enumFrom(first: A): LazyList[A] = + partialNext(first) match { + case Some(by) => + enumFromThen(first, by) + case _ => + LazyList(first) + } + + override final def partialOrder: PartialOrder[A] = order +} + +object Countable { + def apply[A](implicit A: Countable[A]): Countable[A] = A +} + /** * A typeclass with an operation which returns a member which is * greater or `None` than the one supplied. @@ -31,6 +186,26 @@ import scala.{specialized => sp} trait PartialNext[@sp A] { def partialOrder: PartialOrder[A] def partialNext(a: A): Option[A] + + def partialNextByN(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) + } } /** @@ -49,6 +224,26 @@ trait Next[@sp A] extends PartialNext[A] { trait PartialPrevious[@sp A] { def partialOrder: PartialOrder[A] def partialPrevious(a: A): Option[A] + + def partialPreviousByN(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) + } } /** diff --git a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala index a0c3b5366e..2ffa572326 100644 --- a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala @@ -23,7 +23,7 @@ package cats.kernel package instances trait IntInstances { - implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with BoundedEnumerable[Int] = + implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with BoundedEnumerable[Int] with Countable[Int] = new IntOrder implicit val catsKernelStdGroupForInt: CommutativeGroup[Int] = new IntGroup } @@ -47,7 +47,7 @@ trait IntBounded extends LowerBounded[Int] with UpperBounded[Int] { override def maxBound: Int = Int.MaxValue } -class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumerable { self => +class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumerable with Countable[Int] { 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 @@ -65,4 +65,12 @@ class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumera java.lang.Math.max(x, y) override val order: Order[Int] = self + + override final def fromEnum(a: Int): BigInt = BigInt(a) + override final def toEnum(i: BigInt): Option[Int] = + if (i <= BigInt(Int.MaxValue) || i >= BigInt(Int.MinValue)) { + Some(i.toInt) + } else { + None + } } From bbd6664ddf215ff7c2e45b4e760b7909efc31016 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Thu, 17 Nov 2022 08:02:57 -0700 Subject: [PATCH 02/11] Cleanup comments, add LazyListCompat, add numeric step functions --- .../ScalaVersionSpecificLazyListCompat.scala | 9 ++ .../ScalaVersionSpecificLazyListCompat.scala | 9 ++ .../main/scala/cats/kernel/Enumerable.scala | 112 +++++++++++------- .../scala/cats/kernel/LazyListCompat.scala | 10 ++ .../cats/kernel/instances/IntInstances.scala | 4 +- 5 files changed, 102 insertions(+), 42 deletions(-) create mode 100644 kernel/src/main/scala-2.12/cats/kernel/ScalaVersionSpecificLazyListCompat.scala create mode 100644 kernel/src/main/scala-2.13+/cats/kernel/ScalaVersionSpecificLazyListCompat.scala create mode 100644 kernel/src/main/scala/cats/kernel/LazyListCompat.scala 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/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 25b4d50de4..0af978bafb 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -22,15 +22,15 @@ package cats package kernel -import scala.collection.immutable.LazyList 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 Countable types may be mapped to the natural numbers, being an - * instance of `Countable` implies having a total ordering, e.g. an `Order` + * 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`. @@ -42,52 +42,65 @@ import scala.{specialized => sp} * @see [[https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html]] * @see [[https://en.wikipedia.org/wiki/Countable_set]] */ -trait Countable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ +trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ def order: Order[A] def fromEnum(a: A): BigInt def toEnum(i: BigInt): Option[A] - /** The fundamental function in the `Countable` class. Given a start position, - * an offset, and an optional last position, enumerate the values between - * `first` and `last` (or `MaxValue` or infinity), using a step of `by - - * first`. + /** 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> Countable[Int].enumFromThenToOpt(1, 3, Some(11)).toList + * 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, by: A, last: Option[A]): LazyList[A] = { + def enumFromThenToOpt(first: A, second: A, last: Option[A]): LazyListLike.T[A] = { val Zero: BigInt = BigInt(0) - val increment: BigInt = fromEnum(by) - fromEnum(first) + val increment: BigInt = fromEnum(second) - fromEnum(first) - def loop(i: A): LazyList[A] = + def loop(i: A): LazyListLike.T[A] = if (increment > Zero) { // forwards partialNextByN(i, increment) match { case Some(next) => if (last.fold(false)(order.gt(next, _))) { - LazyList.empty[A] + LazyListLike.empty[A] } else { next #:: loop(next) } case _ => - LazyList.empty[A] + LazyListLike.empty[A] } } else { // backwards or zero partialPreviousByN(i, increment.abs) match { case Some(next) => if (last.fold(false)(order.lt(next, _))) { - LazyList.empty + LazyListLike.empty } else { next #:: loop(next) } case _ => - LazyList.empty + LazyListLike.empty } } @@ -96,13 +109,13 @@ trait Countable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ order.compare(first, last) match { case result if result < Zero => if (increment < Zero) { - LazyList.empty[A] + LazyListLike.empty[A] } else { first #:: loop(first) } case result if result > Zero => if (increment > Zero) { - LazyList.empty[A] + LazyListLike.empty[A] } else { first #:: loop(first) } @@ -114,69 +127,88 @@ trait Countable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ } } - /** Given a start position, an offset, and a last position, enumerate the - * values between `first` and `last`, using a step of `by - first`. + def enumFromByToOpt(first: A, step: BigInt, last: Option[A]): LazyListLike.T[A] = + toEnum(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> Countable[Int].enumFromThenTo(1, 3, 11).toList + * 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, by: A, last: A): LazyList[A] = - enumFromThenToOpt(first, by, Some(last)) + def enumFromThenTo(first: A, second: A, last: A): LazyListLike.T[A] = + enumFromThenToOpt(first, second, Some(last)) - /** Given a start position and a last position, enumerate the + 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> Countable[Int].enumFromTo(1, 5).toList + * scala> Enumerable[Int].enumFromTo(1, 5).toList * val res0: List[Int] = List(1, 2, 3, 4, 5) * }}} */ - def enumFromTo(first: A, last: A): LazyList[A] = + 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)) { - LazyList(first) + LazyListLike(first) } else { - LazyList.empty + LazyListLike.empty } } - /** Given a start position and a increment, enumerate the values starting at - * `first` until `MaxValue` or infinity if the type is unbounded. + /** 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 `BigInt`, then this will be an infinite result. * * {{{ - * scala> Countable[Int].enumFromThen(Int.MaxValue - 5, Int.MaxValue - 4).toList + * 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, by: A): LazyList[A] = - enumFromThenToOpt(first, by, None) + 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 start position, enumerate the values starting at `first` by 1, - * until `MaxValue` or infinity if the type is unbounded. + /** Given a first element, enumerate all values in the domain starting at + * first using a step of 1 between all elements. If the domain is infinite, + * e.g. natural numbers or `BigInt`, then this will be an infinite result. * * {{{ - * scala> Countable[Int].enumFrom(Int.MaxValue - 5).toList + * scala> Enumerable[Int].enumFrom(Int.MaxValue - 5).toList * val res0: List[Int] = List(2147483642, 2147483643, 2147483644, 2147483645, 2147483646, 2147483647) * }}} */ - def enumFrom(first: A): LazyList[A] = + def enumFrom(first: A): LazyListLike.T[A] = partialNext(first) match { case Some(by) => enumFromThen(first, by) case _ => - LazyList(first) + LazyListLike(first) } override final def partialOrder: PartialOrder[A] = order } -object Countable { - def apply[A](implicit A: Countable[A]): Countable[A] = A +object Enumerable { + def apply[A](implicit A: Enumerable[A]): Enumerable[A] = A } /** 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/instances/IntInstances.scala b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala index 2ffa572326..781bd55da9 100644 --- a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala @@ -23,7 +23,7 @@ package cats.kernel package instances trait IntInstances { - implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with BoundedEnumerable[Int] with Countable[Int] = + implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with BoundedEnumerable[Int] with Enumerable[Int] = new IntOrder implicit val catsKernelStdGroupForInt: CommutativeGroup[Int] = new IntGroup } @@ -47,7 +47,7 @@ trait IntBounded extends LowerBounded[Int] with UpperBounded[Int] { override def maxBound: Int = Int.MaxValue } -class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumerable with Countable[Int] { self => +class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumerable with Enumerable[Int] { 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 From be74ffc69b91e9e42ee4b144a2bae9f012f352dc Mon Sep 17 00:00:00 2001 From: David Strawn Date: Thu, 17 Nov 2022 11:19:14 -0700 Subject: [PATCH 03/11] Add BoundlessEnumerable, nextOrMin, and previousOrMax --- .../cats/kernel/EnumerableCompat.scala | 4 ++ .../cats/kernel/EnumerableCompat.scala | 4 ++ .../main/scala/cats/kernel/Enumerable.scala | 49 +++++++++++++++++-- .../cats/kernel/instances/IntInstances.scala | 2 +- 4 files changed, 53 insertions(+), 6 deletions(-) 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.13+/cats/kernel/EnumerableCompat.scala b/kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala index 8a782e5ae3..c0fe84984b 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 Enumerable", 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: 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 Enumerable", 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: LazyList[A] = { def loop(a: A): LazyList[A] = partialNext(a) match { diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index 0af978bafb..9dd7cfb843 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -45,7 +45,7 @@ import cats.kernel.{ScalaVersionSpecificLazyListCompat => LazyListLike} trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ def order: Order[A] def fromEnum(a: A): BigInt - def toEnum(i: BigInt): Option[A] + 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 @@ -128,7 +128,7 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ } def enumFromByToOpt(first: A, step: BigInt, last: Option[A]): LazyListLike.T[A] = - toEnum(step).fold( + toEnumOpt(step).fold( LazyListLike.empty[A] )(second => enumFromThenToOpt(first, second, last) @@ -204,6 +204,20 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ LazyListLike(first) } + def membersDescending(implicit A: UpperBounded[A]): LazyListLike.T[A] = + partialPrevious(A.maxBound).fold( + LazyListLike.empty[A] + )(previous => + enumFromThen(A.maxBound, previous) + ) + + def membersAscending(implicit A: LowerBounded[A]): LazyListLike.T[A] = + partialNext(A.minBound).fold( + LazyListLike.empty[A] + )(next => + enumFromThen(A.minBound, next) + ) + override final def partialOrder: PartialOrder[A] = order } @@ -238,6 +252,9 @@ trait PartialNext[@sp A] { loop(a, n) } + + def nextOrMin(a: A)(implicit A: LowerBounded[A]): A = + partialNext(a).getOrElse(A.minBound) } /** @@ -246,6 +263,10 @@ trait PartialNext[@sp A] { */ trait Next[@sp A] extends PartialNext[A] { def next(a: A): A + + override final def nextOrMin(a: A)(implicit A: LowerBounded[A]): A = + next(a) + override def partialNext(a: A): Option[A] = Some(next(a)) } @@ -276,6 +297,9 @@ trait PartialPrevious[@sp A] { loop(a, n) } + + def previousOrMax(a: A)(implicit A: UpperBounded[A]): A = + partialPrevious(a).getOrElse(A.maxBound) } /** @@ -285,6 +309,10 @@ trait PartialPrevious[@sp A] { trait Previous[@sp A] extends PartialPrevious[A] { def partialOrder: PartialOrder[A] def previous(a: A): A + + override final def previousOrMax(a: A)(implicit A: UpperBounded[A]): A = + previous(a) + override def partialPrevious(a: A): Option[A] = Some(previous(a)) } @@ -292,22 +320,33 @@ 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 extened 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)) +} + 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 instead.", since = "2.10.0") def cycleNext(a: A): A = - partialNext(a).getOrElse(minBound) + nextOrMin(a)(this) + @deprecated(message = "Please use previousOrMax instead.", since = "2.10.0") def cyclePrevious(a: A): A = - partialPrevious(a).getOrElse(maxBound) - + previousOrMax(a)(this) } object BoundedEnumerable { diff --git a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala index 781bd55da9..d26d22286e 100644 --- a/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/IntInstances.scala @@ -67,7 +67,7 @@ class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumera override val order: Order[Int] = self override final def fromEnum(a: Int): BigInt = BigInt(a) - override final def toEnum(i: BigInt): Option[Int] = + override final def toEnumOpt(i: BigInt): Option[Int] = if (i <= BigInt(Int.MaxValue) || i >= BigInt(Int.MinValue)) { Some(i.toInt) } else { From 380ecd7ba7c15845430c36a94dc8c0ce34c08e06 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Thu, 17 Nov 2022 18:04:59 -0700 Subject: [PATCH 04/11] Add size to Enumerable --- kernel/src/main/scala/cats/kernel/Enumerable.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index 9dd7cfb843..8105ea19b4 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -218,6 +218,9 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ enumFromThen(A.minBound, next) ) + def size(implicit A: LowerBounded[A], B: UpperBounded[A]): BigInt = + (fromEnum(B.maxBound) - fromEnum(A.minBound)) + BigInt(1) + override final def partialOrder: PartialOrder[A] = order } From c0432f867f104d6b45bd1b3a230f3ebaface867c Mon Sep 17 00:00:00 2001 From: David Strawn Date: Thu, 17 Nov 2022 20:13:33 -0700 Subject: [PATCH 05/11] Clarify Comment --- kernel/src/main/scala/cats/kernel/Enumerable.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index 8105ea19b4..98a781285b 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -174,7 +174,7 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ /** 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 `BigInt`, then this will be an infinite result. + * numbers or integers, then this will be an infinite result. * * {{{ * scala> Enumerable[Int].enumFromThen(Int.MaxValue - 5, Int.MaxValue - 4).toList @@ -189,7 +189,7 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ /** Given a first element, enumerate all values in the domain starting at * first using a step of 1 between all elements. If the domain is infinite, - * e.g. natural numbers or `BigInt`, then this will be an infinite result. + * e.g. natural numbers or integers, then this will be an infinite result. * * {{{ * scala> Enumerable[Int].enumFrom(Int.MaxValue - 5).toList From 9465afffe5924b609dffd8a760d933ee925b30c1 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Thu, 17 Nov 2022 20:15:51 -0700 Subject: [PATCH 06/11] Typo In Comment --- kernel/src/main/scala/cats/kernel/Enumerable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index 98a781285b..6d1794ae0a 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -325,7 +325,7 @@ trait Previous[@sp A] extends PartialPrevious[A] { */ // TODO: Not sure what to do about UnboundedEnumerable. It should extend // Enumerable, but we can't do that without breaking -// bincompat. BoundlessEnumerable could extened UnboundedEnumerable, but that +// bincompat. BoundlessEnumerable could extend UnboundedEnumerable, but that // seems silly... trait UnboundedEnumerable[@sp A] extends Next[A] with Previous[A] { def order: Order[A] From ca66fcb65a814cfcec773babe23e96f8d5e3c619 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Fri, 18 Nov 2022 07:57:45 -0700 Subject: [PATCH 07/11] BigInt instance and initial pass at laws --- .../cats/kernel/laws/EnumerableLaws.scala | 46 +++++++++++++++++++ .../laws/discipline/EnumerableTests.scala | 38 +++++++++++++++ .../scala/cats/kernel/laws/LawTests.scala | 4 ++ .../main/scala/cats/kernel/Enumerable.scala | 15 +++++- .../kernel/instances/BigIntInstances.scala | 9 +++- 5 files changed, 108 insertions(+), 4 deletions(-) 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..a021324514 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,52 @@ 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 +} + +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..43a8ab1c6f 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] 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..50efa0dea6 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 @@ -211,6 +211,10 @@ class Tests extends TestsConfig with DisciplineSuite { checkAll("UpperBounded[FiniteDuration]", UpperBoundedTests[FiniteDuration].upperBounded) checkAll("UpperBounded[UUID]", UpperBoundedTests[UUID].upperBounded) + checkAll("Enumerable[Int]", EnumerableTests[Int].enumerable) + + checkAll("BoundlessEnumerable[BigInt]", BoundlessEnumerableTests[BigInt].boundlessEnumerable) + checkAll("BoundedEnumerable[Unit]", BoundedEnumerableTests[Unit].boundedEnumerable) checkAll("BoundedEnumerable[Boolean]", BoundedEnumerableTests[Boolean].boundedEnumerable) checkAll("BoundedEnumerable[Byte]", BoundedEnumerableTests[Byte].boundedEnumerable) diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index 6d1794ae0a..a38f78ad05 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -188,8 +188,9 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ enumFromByToOpt(first, by, None) /** Given a first element, enumerate all values in the domain starting at - * first using a step of 1 between all elements. If the domain is infinite, - * e.g. natural numbers or integers, then this will be an infinite result. + * 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 @@ -226,6 +227,9 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ object Enumerable { def apply[A](implicit A: Enumerable[A]): Enumerable[A] = A + + implicit def catsKernelEnumerableForInt: Enumerable[Int] = + cats.kernel.instances.int.catsKernelStdOrderForInt } /** @@ -338,6 +342,13 @@ trait BoundlessEnumerable[@sp A] extends Enumerable[A] with Next[A] with Previou override final def toEnumOpt(i: BigInt): Option[A] = Some(toEnum(i)) } +object BoundlessEnumerable { + def apply[A: BoundlessEnumerable](implicit A: BoundlessEnumerable[A]): BoundlessEnumerable[A] = A + + implicit def catsKernelBoundlessEnumerableForInt: BoundlessEnumerable[BigInt] = + cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt +} + trait BoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with PartialNextLowerBounded[A] { def order: Order[A] 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) From 208587de904b09f26f2d7385ff7d3343b7a26e52 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Tue, 22 Nov 2022 08:10:54 -0700 Subject: [PATCH 08/11] More Enumerable Instances, Laws, And Docs --- .../cats/kernel/laws/EnumerableLaws.scala | 14 ++ .../laws/discipline/EnumerableTests.scala | 2 + .../scala/cats/kernel/laws/LawTests.scala | 23 ++-- .../cats/kernel/EnumerableCompat.scala | 8 +- .../main/scala/cats/kernel/Enumerable.scala | 126 +++++++++++++++--- .../kernel/instances/BooleanInstances.scala | 32 ++++- .../cats/kernel/instances/ByteInstances.scala | 27 +++- .../cats/kernel/instances/CharInstances.scala | 28 +++- .../cats/kernel/instances/IntInstances.scala | 35 +++-- .../cats/kernel/instances/LongInstances.scala | 28 +++- .../kernel/instances/ShortInstances.scala | 30 ++++- .../cats/kernel/instances/UnitInstances.scala | 20 ++- 12 files changed, 316 insertions(+), 57 deletions(-) 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 a021324514..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 @@ -33,6 +33,20 @@ trait EnumerableLaws[A] extends PartialNextLaws[A] with PartialPreviousLaws[A] { 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 { 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 43a8ab1c6f..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 @@ -94,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] @@ -119,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 50efa0dea6..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 @@ -215,22 +216,22 @@ class Tests extends TestsConfig with DisciplineSuite { checkAll("BoundlessEnumerable[BigInt]", BoundlessEnumerableTests[BigInt].boundlessEnumerable) - 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("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.13+/cats/kernel/EnumerableCompat.scala b/kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala index c0fe84984b..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,13 +25,13 @@ package kernel import scala.{specialized => sp} import scala.collection.immutable.LazyList -@deprecated(message = "Please use Enumerable", since = "2.10.0") +@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 Enumerable.membersDescending.", since = "2.10.0") + @deprecated(message = "Please use UpperBoundableEnumerable.enumFromMax.", since = "2.10.0") def membersDescending: LazyList[A] = { def loop(a: A): LazyList[A] = partialPrevious(a) match { @@ -43,13 +43,13 @@ trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with Partial } -@deprecated(message = "Please use Enumerable", since = "2.10.0") +@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 Enumerable.membersAscending.", since = "2.10.0") + @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/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index a38f78ad05..cfe5108479 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -35,6 +35,14 @@ import cats.kernel.{ScalaVersionSpecificLazyListCompat => LazyListLike} * 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. @@ -44,7 +52,16 @@ import cats.kernel.{ScalaVersionSpecificLazyListCompat => LazyListLike} */ 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` @@ -204,32 +221,45 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ case _ => LazyListLike(first) } +} - def membersDescending(implicit A: UpperBounded[A]): LazyListLike.T[A] = - partialPrevious(A.maxBound).fold( - LazyListLike.empty[A] - )(previous => - enumFromThen(A.maxBound, previous) - ) +object Enumerable { + def apply[A](implicit A: Enumerable[A]): Enumerable[A] = A - def membersAscending(implicit A: LowerBounded[A]): LazyListLike.T[A] = - partialNext(A.minBound).fold( - LazyListLike.empty[A] - )(next => - enumFromThen(A.minBound, next) - ) + def reverse[A](A: Enumerable[A]): Enumerable[A] = + new Enumerable[A] { + override def fromEnum(a: A): BigInt = + -A.fromEnum(a) - def size(implicit A: LowerBounded[A], B: UpperBounded[A]): BigInt = - (fromEnum(B.maxBound) - fromEnum(A.minBound)) + BigInt(1) + override def toEnumOpt(i: BigInt): Option[A] = + A.toEnumOpt(-i) - override final def partialOrder: PartialOrder[A] = order -} + override val order: Order[A] = + Order.reverse(A.order) -object Enumerable { - def apply[A](implicit A: Enumerable[A]): Enumerable[A] = A + 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.catsKernelStdOrderForInt + 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 } /** @@ -343,12 +373,45 @@ trait BoundlessEnumerable[@sp A] extends Enumerable[A] with Next[A] with Previou } object BoundlessEnumerable { - def apply[A: BoundlessEnumerable](implicit A: BoundlessEnumerable[A]): BoundlessEnumerable[A] = A + def apply[A](implicit A: BoundlessEnumerable[A]): BoundlessEnumerable[A] = A implicit def catsKernelBoundlessEnumerableForInt: BoundlessEnumerable[BigInt] = cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt } +trait LowerBoundableEnumerable[@sp A] extends Enumerable[A] with LowerBounded[A] { + + /** 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: LazyListLike.T[A] = + partialNext(minBound).fold( + LazyListLike.empty[A] + )(next => + enumFromThen(minBound, next) + ) +} + +trait UpperBoundableEnumerable[@sp A] extends Enumerable[A] with UpperBounded[A] { + + /** 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: LazyListLike.T[A] = + partialPrevious(maxBound).fold( + LazyListLike.empty[A] + )(previous => + enumFromThen(maxBound, previous) + ) +} + +@deprecated(message = "Please use BoundableEnumerable instead.", since = "2.10.0") trait BoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with PartialNextLowerBounded[A] { def order: Order[A] @@ -363,6 +426,7 @@ trait BoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with Parti previousOrMax(a)(this) } +@deprecated(message = "Please use BoundableEnumerable instead.", since = "2.10.0") object BoundedEnumerable { implicit def catsKernelBoundedEnumerableForUnit: BoundedEnumerable[Unit] = cats.kernel.instances.unit.catsKernelStdOrderForUnit @@ -397,11 +461,33 @@ object BoundedEnumerable { } } +trait BoundableEnumerable[@sp A] extends UpperBoundableEnumerable[A] with LowerBoundableEnumerable[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 Next[A] and LowerBoundableEnumerable[A] 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 Next[A] and UpperBoundableEnumerable[A] 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/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 d26d22286e..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] with Enumerable[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 with Enumerable[Int] { 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 @@ -65,12 +88,4 @@ class IntOrder extends Order[Int] with Hash[Int] with IntBounded with IntEnumera java.lang.Math.max(x, y) override val order: Order[Int] = self - - override final def fromEnum(a: Int): BigInt = BigInt(a) - override final def toEnumOpt(i: BigInt): Option[Int] = - if (i <= BigInt(Int.MaxValue) || i >= BigInt(Int.MinValue)) { - Some(i.toInt) - } else { - None - } } 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 From 59510b283c8b622552a1756bfc3b49e89a641981 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Tue, 22 Nov 2022 15:35:20 -0700 Subject: [PATCH 09/11] Incomplete encoding of cycleForward functions --- .../main/scala/cats/kernel/Enumerable.scala | 132 +++++++++++++----- 1 file changed, 95 insertions(+), 37 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index cfe5108479..5cfeca5000 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -221,6 +221,32 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ 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 { @@ -270,6 +296,9 @@ 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 partialNextByN(a: A, n: BigInt): Option[A] = { val Zero: BigInt = BigInt(0) val One: BigInt= BigInt(1) @@ -290,8 +319,69 @@ trait PartialNext[@sp A] { loop(a, n) } + def nextOrMinByN(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 = - partialNext(a).getOrElse(A.minBound) + nextOrMinByN(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 cycleForwardFromByN(start: A, n: BigInt)(implicit A: LowerBounded[A]): LazyListLike.T[A] = + start #:: cycleForwardFromBy(nextOrMinByN(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 cycleForwardByN(n: BigInt)(implicit A: LowerBounded[A]): LazyListLike.T[A] = + cycleForwardFromByN(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) } /** @@ -379,45 +469,13 @@ object BoundlessEnumerable { cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt } -trait LowerBoundableEnumerable[@sp A] extends Enumerable[A] with LowerBounded[A] { - - /** 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: LazyListLike.T[A] = - partialNext(minBound).fold( - LazyListLike.empty[A] - )(next => - enumFromThen(minBound, next) - ) -} - -trait UpperBoundableEnumerable[@sp A] extends Enumerable[A] with UpperBounded[A] { - - /** 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: LazyListLike.T[A] = - partialPrevious(maxBound).fold( - LazyListLike.empty[A] - )(previous => - enumFromThen(maxBound, previous) - ) -} - @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 instead.", since = "2.10.0") + @deprecated(message = "Please use nextOrMin.", since = "2.10.0") def cycleNext(a: A): A = nextOrMin(a)(this) @@ -461,7 +519,7 @@ object BoundedEnumerable { } } -trait BoundableEnumerable[@sp A] extends UpperBoundableEnumerable[A] with LowerBoundableEnumerable[A] { +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 @@ -481,13 +539,13 @@ object BoundableEnumerable { def apply[A](implicit A: BoundableEnumerable[A]): BoundableEnumerable[A] = A } -@deprecated(message = "Please use Next[A] and LowerBoundableEnumerable[A] instead.", since = "2.10.0") +@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 Next[A] and UpperBoundableEnumerable[A] instead.", since = "2.10.0") +@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 From 13380af3bd624c2dd526e55465d95fb2e81f48aa Mon Sep 17 00:00:00 2001 From: David Strawn Date: Wed, 23 Nov 2022 08:43:01 -0700 Subject: [PATCH 10/11] squash! --- .../main/scala/cats/kernel/Enumerable.scala | 105 +++++++++------- .../scala/cats/kernel/PartialPrevious.scala | 67 ++++++++++ .../kernel/UpperBoundedPartialPrevious.scala | 117 ++++++++++++++++++ 3 files changed, 242 insertions(+), 47 deletions(-) create mode 100644 kernel/src/main/scala/cats/kernel/PartialPrevious.scala create mode 100644 kernel/src/main/scala/cats/kernel/UpperBoundedPartialPrevious.scala diff --git a/kernel/src/main/scala/cats/kernel/Enumerable.scala b/kernel/src/main/scala/cats/kernel/Enumerable.scala index 5cfeca5000..d4c9069d65 100644 --- a/kernel/src/main/scala/cats/kernel/Enumerable.scala +++ b/kernel/src/main/scala/cats/kernel/Enumerable.scala @@ -97,7 +97,7 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ def loop(i: A): LazyListLike.T[A] = if (increment > Zero) { // forwards - partialNextByN(i, increment) match { + partialNextBy(i, increment) match { case Some(next) => if (last.fold(false)(order.gt(next, _))) { LazyListLike.empty[A] @@ -109,7 +109,7 @@ trait Enumerable[@sp A] extends PartialNext[A] with PartialPrevious[A]{ } } else { // backwards or zero - partialPreviousByN(i, increment.abs) match { + partialPreviousBy(i, increment.abs) match { case Some(next) => if (last.fold(false)(order.lt(next, _))) { LazyListLike.empty @@ -299,7 +299,7 @@ trait PartialNext[@sp A] { /** As [[#partialNext]], but rather than getting the next element, it gets the * Nth next element. */ - def partialNextByN(a: A, n: BigInt): Option[A] = { + def partialNextBy(a: A, n: BigInt): Option[A] = { val Zero: BigInt = BigInt(0) val One: BigInt= BigInt(1) @@ -319,7 +319,13 @@ trait PartialNext[@sp A] { loop(a, n) } - def nextOrMinByN(a: A, n: BigInt)(implicit A: LowerBounded[A]): A = { + /** 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) @@ -341,15 +347,15 @@ trait PartialNext[@sp A] { /** Get the next value if defined, otherwise get the minBound. */ def nextOrMin(a: A)(implicit A: LowerBounded[A]): A = - nextOrMinByN(a, BigInt(1)) + 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 cycleForwardFromByN(start: A, n: BigInt)(implicit A: LowerBounded[A]): LazyListLike.T[A] = - start #:: cycleForwardFromBy(nextOrMinByN(start, n), n) + 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 @@ -370,8 +376,8 @@ trait PartialNext[@sp A] { cycleForwardFromBy(start, BigInt(1)) /** As [[#cycleForwardFromByN]], but uses the minBound as the start value. */ - def cycleForwardByN(n: BigInt)(implicit A: LowerBounded[A]): LazyListLike.T[A] = - cycleForwardFromByN(A.minBound, n) + 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. * @@ -384,51 +390,59 @@ trait PartialNext[@sp 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 +} + /** * A typeclass with an operation which returns a member which is * always greater than the one supplied. */ trait Next[@sp A] extends PartialNext[A] { def next(a: A): A - - override final def nextOrMin(a: A)(implicit A: LowerBounded[A]): A = - next(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] - - def partialPreviousByN(a: A, n: BigInt): Option[A] = { - val Zero: BigInt = BigInt(0) - val One: BigInt = BigInt(1) +object Next { + def apply[A](implicit A: Next[A]): Next[A] = A - @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) - } - - def previousOrMax(a: A)(implicit A: UpperBounded[A]): A = - partialPrevious(a).getOrElse(A.maxBound) + 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. @@ -437,9 +451,6 @@ trait Previous[@sp A] extends PartialPrevious[A] { def partialOrder: PartialOrder[A] def previous(a: A): A - override final def previousOrMax(a: A)(implicit A: UpperBounded[A]): A = - previous(a) - override def partialPrevious(a: A): Option[A] = Some(previous(a)) } @@ -477,11 +488,11 @@ trait BoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with Parti @deprecated(message = "Please use nextOrMin.", since = "2.10.0") def cycleNext(a: A): A = - nextOrMin(a)(this) + partialNext(a).getOrElse(minBound) @deprecated(message = "Please use previousOrMax instead.", since = "2.10.0") def cyclePrevious(a: A): A = - previousOrMax(a)(this) + partialPrevious(a).getOrElse(maxBound) } @deprecated(message = "Please use BoundableEnumerable instead.", since = "2.10.0") 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 +} From c01378882e1af2a042b486c369359338caeac3b3 Mon Sep 17 00:00:00 2001 From: David Strawn Date: Thu, 8 Dec 2022 16:13:03 -0700 Subject: [PATCH 11/11] squash! --- .../scala/cats/kernel/PartialForward.scala | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 kernel/src/main/scala/cats/kernel/PartialForward.scala 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) + } +}