Skip to content

Commit

Permalink
Add LowerBounded and UpperBounded typeclasses (#2913)
Browse files Browse the repository at this point in the history
* Add LowerBounded and UpperBounded typeclasses

* Add instances for LowerBounded and UpperBounded typeclasses
  • Loading branch information
izeigerman authored and kailuowang committed Jun 27, 2019
1 parent e2c255e commit 22a8cc6
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 30 deletions.
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 @@ -177,6 +177,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

0 comments on commit 22a8cc6

Please sign in to comment.