Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Enum typeclass #3458

Merged
merged 15 commits into from
Jul 6, 2020
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cats.kernel
package laws

trait PartialPreviousLaws[A] extends PartialOrderLaws[A] {

implicit def P: PartialPrevious[A]

def previousOrderWeak(a: A): IsEq[Boolean] =
P.partialPrevious(a).map(E.lt(_, a)).getOrElse(true) <-> true

def previousOrderStrong(a: A, b: A): IsEq[Option[Boolean]] =
if (E.gt(a, b)) {
P.partialPrevious(a).map(E.gteqv(_, b)) <-> Option(true)
} else {
Option.empty <-> Option.empty
}

}

object PartialPreviousLaws {
def apply[A](implicit ev: PartialPrevious[A]): PartialPreviousLaws[A] =
new PartialPreviousLaws[A] {
def E: PartialOrder[A] = ev.partialOrder
def P: PartialPrevious[A] = ev
}
}

trait PartialNextLaws[A] extends PartialOrderLaws[A] {

implicit def N: PartialNext[A]

def nextOrderWeak(a: A): IsEq[Boolean] =
N.partialNext(a).map(E.gt(_, a)).getOrElse(true) <-> true

def nextOrderStrong(a: A, b: A): IsEq[Option[Boolean]] =
if (E.lt(a, b)) {
N.partialNext(a).map(E.lteqv(_, b)) <-> Option(true)
} else {
Option(true) <-> Option(true)
}

}

trait PartialNextBoundedLaws[A] extends PartialNextLaws[A] with UpperBoundedLaws[A] {

def minBoundTerminal: IsEq[Option[A]] =
N.partialNext(B.maxBound) <-> None

}

trait PartialPreviousNextLaws[A] extends PartialNextLaws[A] with PartialPreviousLaws[A] with OrderLaws[A] {

def partialLeftIdentity(a: A): IsEq[Option[A]] =
P.partialPrevious(a)
.map(N.partialNext(_) <-> Some(a))
.getOrElse(Option.empty <-> Option.empty)

def partialRightIdentity(a: A): IsEq[Option[A]] =
N.partialNext(a)
.map(P.partialPrevious(_) <-> Some(a))
.getOrElse(Option.empty <-> Option.empty)

}

trait PartialPreviousBoundedLaws[A] extends PartialPreviousLaws[A] with LowerBoundedLaws[A] {

def maxBoundTerminal: IsEq[Option[A]] =
P.partialPrevious(B.minBound) <-> None

}

trait BoundedEnumerableLaws[A]
extends PartialPreviousNextLaws[A]
with PartialPreviousBoundedLaws[A]
with PartialNextBoundedLaws[A] {
override def B: LowerBounded[A] with UpperBounded[A]
}

object BoundedEnumerableLaws {
def apply[A](implicit ev: BoundedEnumerable[A]): BoundedEnumerableLaws[A] =
new BoundedEnumerableLaws[A] {
val B: LowerBounded[A] with UpperBounded[A] = ev
val E = ev.order
val N = ev
val P = ev
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package cats
package kernel
package laws
package discipline

import cats.kernel.instances.boolean._
import org.scalacheck.{Arbitrary, Prop}
import org.scalacheck.Prop.forAll

trait PartialNextTests[A] extends PartialOrderTests[A] {

def laws: PartialNextLaws[A]

def partialNext(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqOA: Eq[Option[A]], eqA: Eq[A]): RuleSet =
new DefaultRuleSet(
"partialNext",
Some(partialOrder),
"next(a) > a" -> forAll(laws.nextOrderWeak _),
"forall a, b. if a < b. next(a) <= b" -> forAll(laws.nextOrderStrong _)
)

}

trait PartialPreviousTests[A] extends PartialOrderTests[A] {

def laws: PartialPreviousLaws[A]

def partialPrevious(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqOA: Eq[Option[A]], eqA: Eq[A]): RuleSet =
new DefaultRuleSet(
"partialPrevious",
Some(partialOrder),
"next(a) > a" -> forAll(laws.previousOrderWeak _),
"forall a, b. if a < b. next(a) <= b" -> forAll(laws.previousOrderStrong _)
)

}

trait BoundedEnumerableTests[A] extends OrderTests[A] with PartialNextTests[A] with PartialPreviousTests[A] {

def laws: BoundedEnumerableLaws[A]

def boundedEnumerable(implicit
arbA: Arbitrary[A],
arbF: Arbitrary[A => A],
eqOA: Eq[Option[A]],
eqA: Eq[A]
): RuleSet =
new RuleSet {
val name: String = "boundedEnumerable"
val bases: Seq[(String, RuleSet)] = Nil
val parents: Seq[RuleSet] = Seq(partialNext, partialPrevious, order)
val props: Seq[(String, Prop)] = Seq(
"min bound is terminal" -> laws.minBoundTerminal,
"max bound is terminal" -> laws.maxBoundTerminal,
"partial right identity" -> forAll(laws.partialRightIdentity _),
"partial left identity" -> forAll(laws.partialLeftIdentity _)
)
}

}

object BoundedEnumerableTests {
def apply[A: BoundedEnumerable]: BoundedEnumerableTests[A] =
new BoundedEnumerableTests[A] { def laws: BoundedEnumerableLaws[A] = BoundedEnumerableLaws[A] }
}
25 changes: 7 additions & 18 deletions kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,9 @@ class Tests extends TestsConfig with AnyFunSuiteLike with FunSuiteDiscipline wit
PartialOrderTests(Semilattice.asJoinPartialOrder[Set[Int]]).partialOrder
)

checkAll("Order[Unit]", OrderTests[Unit].order)
checkAll("Order[Boolean]", OrderTests[Boolean].order)
checkAll("Order[String]", OrderTests[String].order)
checkAll("Order[Symbol]", OrderTests[Symbol].order)
checkAll("Order[Byte]", OrderTests[Byte].order)
checkAll("Order[Short]", OrderTests[Short].order)
checkAll("Order[Char]", OrderTests[Char].order)
checkAll("Order[Int]", OrderTests[Int].order)
checkAll("Order[Long]", OrderTests[Long].order)
checkAll("PartialOrder[BitSet]", PartialOrderTests[BitSet].partialOrder)
checkAll("Order[BigInt]", OrderTests[BigInt].order)
checkAll("Order[Duration]", OrderTests[Duration].order)
Expand All @@ -215,30 +209,25 @@ class Tests extends TestsConfig with AnyFunSuiteLike with FunSuiteDiscipline wit
checkAll("Order.reverse(Order.reverse(Order[Int]))", OrderTests(Order.reverse(Order.reverse(Order[Int]))).order)
checkAll("Order.fromLessThan[Int](_ < _)", OrderTests(Order.fromLessThan[Int](_ < _)).order)

checkAll("LowerBounded[Unit]", LowerBoundedTests[Unit].lowerBounded)
checkAll("LowerBounded[Boolean]", LowerBoundedTests[Boolean].lowerBounded)
checkAll("LowerBounded[Byte]", LowerBoundedTests[Byte].lowerBounded)
checkAll("LowerBounded[Short]", LowerBoundedTests[Short].lowerBounded)
checkAll("LowerBounded[Char]", LowerBoundedTests[Char].lowerBounded)
checkAll("LowerBounded[Int]", LowerBoundedTests[Int].lowerBounded)
checkAll("LowerBounded[Long]", LowerBoundedTests[Long].lowerBounded)
checkAll("LowerBounded[Duration]", LowerBoundedTests[Duration].lowerBounded)
checkAll("LowerBounded[FiniteDuration]", LowerBoundedTests[FiniteDuration].lowerBounded)
checkAll("LowerBounded[UUID]", LowerBoundedTests[UUID].lowerBounded)
checkAll("LowerBounded[String]", LowerBoundedTests[String].lowerBounded)
checkAll("LowerBounded[Symbol]", LowerBoundedTests[Symbol].lowerBounded)

checkAll("UpperBounded[Unit]", UpperBoundedTests[Unit].upperBounded)
checkAll("UpperBounded[Boolean]", UpperBoundedTests[Boolean].upperBounded)
checkAll("UpperBounded[Byte]", UpperBoundedTests[Byte].upperBounded)
checkAll("UpperBounded[Short]", UpperBoundedTests[Short].upperBounded)
checkAll("UpperBounded[Char]", UpperBoundedTests[Char].upperBounded)
checkAll("UpperBounded[Int]", UpperBoundedTests[Int].upperBounded)
checkAll("UpperBounded[Long]", UpperBoundedTests[Long].upperBounded)
checkAll("UpperBounded[Duration]", UpperBoundedTests[Duration].upperBounded)
checkAll("UpperBounded[FiniteDuration]", UpperBoundedTests[FiniteDuration].upperBounded)
checkAll("UpperBounded[UUID]", UpperBoundedTests[UUID].upperBounded)

checkAll("BoundedEnumerable[Unit]", BoundedEnumerableTests[Unit].boundedEnumerable)
checkAll("BoundedEnumerable[Boolean]", BoundedEnumerableTests[Boolean].boundedEnumerable)
checkAll("BoundedEnumerable[Short]", BoundedEnumerableTests[Short].boundedEnumerable)
checkAll("BoundedEnumerable[Int]", BoundedEnumerableTests[Int].boundedEnumerable)
checkAll("BoundedEnumerable[Char]", BoundedEnumerableTests[Char].boundedEnumerable)
checkAll("BoundedEnumerable[Long]", BoundedEnumerableTests[Long].boundedEnumerable)

checkAll("Monoid[String]", MonoidTests[String].monoid)
checkAll("Monoid[String]", SerializableTests.serializable(Monoid[String]))
checkAll("Monoid[Option[String]]", MonoidTests[Option[String]].monoid)
Expand Down
37 changes: 37 additions & 0 deletions kernel/src/main/scala-2.12/cats/kernel/EnumerableCompat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cats
package kernel

import scala.{specialized => sp}
import scala.collection.immutable.Stream

trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with UpperBounded[A] {

/**
* Enumerate the members in descending order.
*/
def membersDescending: Stream[A] = {
def loop(a: A): Stream[A] =
partialPrevious(a) match {
case Some(aa) => aa #:: loop(aa)
case _ => Stream.empty
}
maxBound #:: loop(maxBound)
}

}

trait PartialNextLowerBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with LowerBounded[A] {

/**
* Enumerate the members in ascending order.
*/
def membersAscending: Stream[A] = {
def loop(a: A): Stream[A] =
partialNext(a) match {
case Some(aa) => aa #:: loop(aa)
case _ => Stream.empty
}
minBound #:: loop(minBound)
}

}
37 changes: 37 additions & 0 deletions kernel/src/main/scala-2.13+/cats/kernel/EnumerableCompat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cats
package kernel

import scala.{specialized => sp}
import scala.collection.immutable.LazyList

trait PartialPreviousUpperBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with UpperBounded[A] {

/**
* Enumerate the members in descending order.
*/
def membersDescending: LazyList[A] = {
def loop(a: A): LazyList[A] =
partialPrevious(a) match {
case Some(aa) => aa #:: loop(aa)
case _ => LazyList.empty
}
maxBound #:: loop(maxBound)
}

}

trait PartialNextLowerBounded[@sp A] extends PartialPrevious[A] with PartialNext[A] with LowerBounded[A] {

/**
* Enumerate the members in ascending order.
*/
def membersAscending: LazyList[A] = {
def loop(a: A): LazyList[A] =
partialNext(a) match {
case Some(aa) => aa #:: loop(aa)
case _ => LazyList.empty
}
minBound #:: loop(minBound)
}

}
90 changes: 90 additions & 0 deletions kernel/src/main/scala/cats/kernel/Enumerable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package cats
package kernel

import scala.{specialized => sp}

/**
* A typeclass with an operation which returns a member which is
* greater or `None` than the one supplied.
*/
trait PartialNext[@sp A] {
def partialOrder: PartialOrder[A]
def partialNext(a: A): Option[A]
}

/**
* 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 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]
}

/**
* A typeclass with an operation which returns a member which is
* always smaller than the one supplied.
*/
trait Previous[@sp A] extends PartialPrevious[A] {
def partialOrder: PartialOrder[A]
def previous(a: A): A
override def partialPrevious(a: A): Option[A] = Some(previous(a))
}

/**
* A typeclass which has both `previous` and `next` operations
* such that `next . previous == identity`.
*/
trait UnboundedEnumerable[@sp A] extends Next[A] with Previous[A] {
def order: Order[A]
override def partialOrder: PartialOrder[A] = order
}

trait BoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with PartialNextLowerBounded[A] {

def order: Order[A]
override def partialOrder: PartialOrder[A] = order

def cycleNext(a: A): A =
partialNext(a).getOrElse(minBound)

def cyclePrevious(a: A): A =
partialPrevious(a).getOrElse(maxBound)

}

object BoundedEnumerable {
implicit def catsKernelBoundedEnumerableForUnit: BoundedEnumerable[Unit] =
cats.kernel.instances.unit.catsKernelStdOrderForUnit
implicit def catsKernelBoundedEnumerableForBoolean: BoundedEnumerable[Boolean] =
cats.kernel.instances.boolean.catsKernelStdOrderForBoolean
implicit def catsKernelBoundedEnumerableForInt: BoundedEnumerable[Int] =
cats.kernel.instances.int.catsKernelStdOrderForInt
implicit def catsKernelBoundedEnumerableForShort: BoundedEnumerable[Short] =
cats.kernel.instances.short.catsKernelStdOrderForShort
implicit def catsKernelBoundedEnumerableForLong: BoundedEnumerable[Long] =
cats.kernel.instances.long.catsKernelStdOrderForLong
implicit def catsKernelBoundedEnumerableForChar: BoundedEnumerable[Char] =
cats.kernel.instances.char.catsKernelStdOrderForChar

@inline def apply[A](implicit e: BoundedEnumerable[A]): BoundedEnumerable[A] = e
}

trait LowerBoundedEnumerable[@sp A] extends PartialNextLowerBounded[A] with Next[A] {
def order: Order[A]
override def partialOrder: PartialOrder[A] = order
}

trait UpperBoundedEnumerable[@sp A] extends PartialPreviousUpperBounded[A] with Previous[A] {
def order: Order[A]
override def partialOrder: PartialOrder[A] = order
}
Loading