Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add LowerBounded and UpperBounded typeclasses #2913

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package cats
package data

import cats.Contravariant
import cats.kernel.{CommutativeMonoid, CommutativeSemigroup}
import cats.kernel.{CommutativeMonoid, CommutativeSemigroup, LowerBounded, UpperBounded}

/**
* [[Const]] is a phantom type, it does not contain a value of its second type parameter `B`
Expand Down Expand Up @@ -58,6 +57,18 @@ object Const extends ConstInstances {
}

sealed abstract private[data] class ConstInstances extends ConstInstances0 {
implicit def catsDataUpperBoundedForConst[A, B](implicit A: UpperBounded[A]): UpperBounded[Const[A, B]] =
new UpperBounded[Const[A, B]] {
override def partialOrder: PartialOrder[Const[A, B]] = catsDataPartialOrderForConst(A.partialOrder)
override def maxBound: Const[A, B] = Const(A.maxBound)
}

implicit def catsDataLowerBoundedForConst[A, B](implicit A: LowerBounded[A]): LowerBounded[Const[A, B]] =
new LowerBounded[Const[A, B]] {
override def partialOrder: PartialOrder[Const[A, B]] = catsDataPartialOrderForConst(A.partialOrder)
override def minBound: Const[A, B] = Const(A.minBound)
}

implicit def catsDataOrderForConst[A: Order, B]: Order[Const[A, B]] = new Order[Const[A, B]] {
def compare(x: Const[A, B], y: Const[A, B]): Int =
x.compare(y)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cats.kernel.laws

import cats.kernel.{LowerBounded, PartialOrder, UpperBounded}

trait LowerBoundedLaws[A] extends PartialOrderLaws[A] {
implicit def B: LowerBounded[A]

def boundLteqv(x: A): IsEq[Boolean] =
E.lteqv(B.minBound, x) <-> true
}

object LowerBoundedLaws {
def apply[A](implicit ev: LowerBounded[A]): LowerBoundedLaws[A] =
new LowerBoundedLaws[A] {
def B: LowerBounded[A] = ev
def E: PartialOrder[A] = ev.partialOrder
}
}

trait UpperBoundedLaws[A] extends PartialOrderLaws[A] {
implicit def B: UpperBounded[A]

def boundGteqv(x: A): IsEq[Boolean] =
E.gteqv(B.maxBound, x) <-> true
}

object UpperBoundedLaws {
def apply[A](implicit ev: UpperBounded[A]): UpperBoundedLaws[A] =
new UpperBoundedLaws[A] {
def B: UpperBounded[A] = ev
def E: PartialOrder[A] = ev.partialOrder
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cats
package kernel
package laws
package discipline

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

trait LowerBoundedTests[A] extends PartialOrderTests[A] {
def laws: LowerBoundedLaws[A]

def lowerBounded(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqOA: Eq[Option[A]], eqA: Eq[A]): RuleSet =
new DefaultRuleSet(
"lowerBounded",
Some(partialOrder),
"bound is less than or equals" -> forAll(laws.boundLteqv _)
)
}

object LowerBoundedTests {
def apply[A: LowerBounded]: LowerBoundedTests[A] =
new LowerBoundedTests[A] { def laws: LowerBoundedLaws[A] = LowerBoundedLaws[A] }
}

trait UpperBoundedTests[A] extends PartialOrderTests[A] {
def laws: UpperBoundedLaws[A]

def upperBounded(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqOA: Eq[Option[A]], eqA: Eq[A]): RuleSet =
new DefaultRuleSet(
"upperBounded",
Some(partialOrder),
"bound is greater than or equals" -> forAll(laws.boundGteqv _)
)
}

object UpperBoundedTests {
def apply[A: UpperBounded]: UpperBoundedTests[A] =
new UpperBoundedTests[A] { def laws: UpperBoundedLaws[A] = UpperBoundedLaws[A] }
}
24 changes: 24 additions & 0 deletions kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,30 @@ class Tests extends AnyFunSuiteLike with Discipline {
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("Monoid[String]", MonoidTests[String].monoid)
checkAll("Monoid[String]", SerializableTests.serializable(Monoid[String]))
checkAll("Monoid[Option[Int]]", MonoidTests[Option[Int]].monoid)
Expand Down
43 changes: 43 additions & 0 deletions kernel/src/main/scala/cats/kernel/Bounded.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package cats.kernel

import scala.{specialized => sp}

/**
* A type class used to name the lower limit of a type.
*/
trait LowerBounded[@sp A] {
def partialOrder: PartialOrder[A]

/**
* Returns the lower limit of a type.
*/
def minBound: A
}

trait LowerBoundedFunctions[L[T] <: LowerBounded[T]] {
def minBound[@sp A](implicit ev: L[A]): A = ev.minBound
}

object LowerBounded extends LowerBoundedFunctions[LowerBounded] {
@inline def apply[A](implicit l: LowerBounded[A]): LowerBounded[A] = l
}

/**
* A type class used to name the upper limit of a type.
*/
trait UpperBounded[@sp A] {
def partialOrder: PartialOrder[A]

/**
* Returns the upper limit of a type.
*/
def maxBound: A
}

trait UpperBoundedFunctions[U[T] <: UpperBounded[T]] {
def maxBound[@sp A](implicit ev: U[A]): A = ev.maxBound
}

object UpperBounded extends UpperBoundedFunctions[UpperBounded] {
@inline def apply[A](implicit u: UpperBounded[A]): UpperBounded[A] = u
}
12 changes: 10 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/BooleanInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package cats.kernel
package instances

trait BooleanInstances {
implicit val catsKernelStdOrderForBoolean: Order[Boolean] with Hash[Boolean] =
implicit val catsKernelStdOrderForBoolean
: Order[Boolean] with Hash[Boolean] with LowerBounded[Boolean] with UpperBounded[Boolean] =
new BooleanOrder
}

class BooleanOrder extends Order[Boolean] with Hash[Boolean] {
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 { self =>

def hash(x: Boolean): Int = x.hashCode()
def compare(x: Boolean, y: Boolean): Int =
Expand All @@ -21,4 +27,6 @@ class BooleanOrder extends Order[Boolean] with Hash[Boolean] {

override def min(x: Boolean, y: Boolean): Boolean = x && y
override def max(x: Boolean, y: Boolean): Boolean = x || y

override val partialOrder: PartialOrder[Boolean] = self
}
12 changes: 10 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/ByteInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package cats.kernel
package instances

trait ByteInstances {
implicit val catsKernelStdOrderForByte: Order[Byte] with Hash[Byte] = new ByteOrder
implicit val catsKernelStdOrderForByte: Order[Byte] with Hash[Byte] with LowerBounded[Byte] with UpperBounded[Byte] =
new ByteOrder
implicit val catsKernelStdGroupForByte: CommutativeGroup[Byte] = new ByteGroup
}

Expand All @@ -13,7 +14,12 @@ class ByteGroup extends CommutativeGroup[Byte] {
override def remove(x: Byte, y: Byte): Byte = (x - y).toByte
}

class ByteOrder extends Order[Byte] with Hash[Byte] {
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 { self =>

def hash(x: Byte): Int = x.hashCode()

Expand All @@ -31,4 +37,6 @@ class ByteOrder extends Order[Byte] with Hash[Byte] {
java.lang.Math.min(x.toInt, y.toInt).toByte
override def max(x: Byte, y: Byte): Byte =
java.lang.Math.max(x.toInt, y.toInt).toByte

override val partialOrder: PartialOrder[Byte] = self
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ trait CharInstances {
implicit val catsKernelStdOrderForChar = new CharOrder
}

class CharOrder extends Order[Char] with Hash[Char] {
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 { 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
Expand All @@ -15,4 +20,6 @@ class CharOrder extends Order[Char] with Hash[Char] {
override def gteqv(x: Char, y: Char): Boolean = x >= y
override def lt(x: Char, y: Char): Boolean = x < y
override def lteqv(x: Char, y: Char): Boolean = x <= y

override val partialOrder: PartialOrder[Char] = self
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ package instances
import scala.concurrent.duration.Duration

trait DurationInstances {
implicit val catsKernelStdOrderForDuration: Order[Duration] with Hash[Duration] = new DurationOrder
implicit val catsKernelStdOrderForDuration
: Order[Duration] with Hash[Duration] with LowerBounded[Duration] with UpperBounded[Duration] = new DurationOrder
implicit val catsKernelStdGroupForDuration: CommutativeGroup[Duration] = new DurationGroup
}

// Duration.Undefined, Duration.Inf, Duration.MinusInf

trait DurationBounded extends LowerBounded[Duration] with UpperBounded[Duration] {
override def minBound: Duration = Duration.MinusInf
override def maxBound: Duration = Duration.Inf
}

/**
* This ordering is valid for all defined durations.
*
* The value Duration.Undefined breaks our laws, because undefined
* values are not equal to themselves.
*/
class DurationOrder extends Order[Duration] with Hash[Duration] {
class DurationOrder extends Order[Duration] with Hash[Duration] with DurationBounded { self =>
def hash(x: Duration): Int = x.hashCode()

def compare(x: Duration, y: Duration): Int = x.compare(y)
Expand All @@ -30,6 +36,8 @@ class DurationOrder extends Order[Duration] with Hash[Duration] {

override def min(x: Duration, y: Duration): Duration = x.min(y)
override def max(x: Duration, y: Duration): Duration = x.max(y)

override val partialOrder: PartialOrder[Duration] = self
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package cats.kernel
package instances

import java.util.concurrent.TimeUnit

import scala.concurrent.duration.{Duration, FiniteDuration}

trait FiniteDurationInstances {
implicit val catsKernelStdOrderForFiniteDuration: Order[FiniteDuration] with Hash[FiniteDuration] =
new FiniteDurationOrder
implicit val catsKernelStdOrderForFiniteDuration: Order[FiniteDuration]
with Hash[FiniteDuration]
with LowerBounded[FiniteDuration]
with UpperBounded[FiniteDuration] = new FiniteDurationOrder
implicit val catsKernelStdGroupForFiniteDuration: CommutativeGroup[FiniteDuration] = new FiniteDurationGroup
}
class FiniteDurationOrder extends Order[FiniteDuration] with Hash[FiniteDuration] {

trait FiniteDurationBounded extends LowerBounded[FiniteDuration] with UpperBounded[FiniteDuration] {
override def minBound: FiniteDuration = FiniteDuration(-Long.MaxValue, TimeUnit.NANOSECONDS)
override def maxBound: FiniteDuration = FiniteDuration(Long.MaxValue, TimeUnit.NANOSECONDS)
}

class FiniteDurationOrder extends Order[FiniteDuration] with Hash[FiniteDuration] with FiniteDurationBounded { self =>
def hash(x: FiniteDuration): Int = x.hashCode()

def compare(x: FiniteDuration, y: FiniteDuration): Int = x.compare(y)
Expand All @@ -22,6 +32,8 @@ class FiniteDurationOrder extends Order[FiniteDuration] with Hash[FiniteDuration

override def min(x: FiniteDuration, y: FiniteDuration): FiniteDuration = x.min(y)
override def max(x: FiniteDuration, y: FiniteDuration): FiniteDuration = x.max(y)

override val partialOrder: PartialOrder[FiniteDuration] = self
}

class FiniteDurationGroup extends CommutativeGroup[FiniteDuration] {
Expand Down
12 changes: 10 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/IntInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package cats.kernel
package instances

trait IntInstances {
implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] = new IntOrder
implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] with LowerBounded[Int] with UpperBounded[Int] =
new IntOrder
implicit val catsKernelStdGroupForInt: CommutativeGroup[Int] = new IntGroup
}

Expand All @@ -13,7 +14,12 @@ class IntGroup extends CommutativeGroup[Int] {
override def remove(x: Int, y: Int): Int = x - y
}

class IntOrder extends Order[Int] with Hash[Int] {
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 { 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
Expand All @@ -29,4 +35,6 @@ class IntOrder extends Order[Int] with Hash[Int] {
java.lang.Math.min(x, y)
override def max(x: Int, y: Int): Int =
java.lang.Math.max(x, y)

override val partialOrder: PartialOrder[Int] = self
}
12 changes: 10 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/LongInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package cats.kernel
package instances

trait LongInstances {
implicit val catsKernelStdOrderForLong: Order[Long] with Hash[Long] = new LongOrder
implicit val catsKernelStdOrderForLong: Order[Long] with Hash[Long] with LowerBounded[Long] with UpperBounded[Long] =
new LongOrder
implicit val catsKernelStdGroupForLong: CommutativeGroup[Long] = new LongGroup
}

Expand All @@ -13,7 +14,12 @@ class LongGroup extends CommutativeGroup[Long] {
override def remove(x: Long, y: Long): Long = x - y
}

class LongOrder extends Order[Long] with Hash[Long] {
trait LongBounded extends LowerBounded[Long] with UpperBounded[Long] {
override def minBound: Long = Long.MinValue
override def maxBound: Long = Long.MaxValue
}

class LongOrder extends Order[Long] with Hash[Long] with LongBounded { self =>

def hash(x: Long): Int = x.hashCode()

Expand All @@ -32,4 +38,6 @@ class LongOrder extends Order[Long] with Hash[Long] {
java.lang.Math.min(x, y)
override def max(x: Long, y: Long): Long =
java.lang.Math.max(x, y)

override val partialOrder: PartialOrder[Long] = self
}
Loading