diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index 57ee14aa15..87ab6b7083 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -8,7 +8,7 @@ import cats.kernel.std.all._ import org.typelevel.discipline.{ Laws } import org.typelevel.discipline.scalatest.Discipline -import org.scalacheck.{ Arbitrary } +import org.scalacheck.{ Arbitrary, Gen } import Arbitrary.arbitrary import org.scalatest.FunSuite import scala.util.Random @@ -79,6 +79,27 @@ class LawTests extends FunSuite with Discipline { laws[GroupLaws, (Int, Int)].check(_.band) laws[GroupLaws, Unit].check(_.boundedSemilattice) + + // Comparison related + implicit val arbitraryComparison: Arbitrary[Comparison] = + Arbitrary(Gen.oneOf(Comparison.GreaterThan, Comparison.EqualTo, Comparison.LessThan)) + + laws[OrderLaws, Comparison].check(_.eqv) + + test("comparison") { + val order = Order[Int] + val eqv = Eq[Comparison] + eqv.eqv(order.comparison(1, 0), Comparison.GreaterThan) && + eqv.eqv(order.comparison(0, 0), Comparison.EqualTo) && + eqv.eqv(order.comparison(-1, 0), Comparison.LessThan) + } + + test("signum . toInt . comparison = signum . compare") { + check { (i: Int, j: Int) => + Eq[Int].eqv(Order[Int].comparison(i, j).toInt.signum, Order[Int].compare(i, j).signum) + } + } + // esoteric machinery follows... implicit lazy val band: Band[(Int, Int)] = diff --git a/kernel/src/main/scala/cats/kernel/Comparison.scala b/kernel/src/main/scala/cats/kernel/Comparison.scala new file mode 100644 index 0000000000..d925964a8d --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Comparison.scala @@ -0,0 +1,19 @@ +package cats.kernel + +/** ADT encoding the possible results of a comparison */ +sealed abstract class Comparison extends Product with Serializable { + /** The signum of this comparison */ + def toInt: Int = this match { + case Comparison.GreaterThan => 1 + case Comparison.EqualTo => 0 + case Comparison.LessThan => -1 + } +} + +object Comparison { + final case object GreaterThan extends Comparison + final case object EqualTo extends Comparison + final case object LessThan extends Comparison + + implicit val catsKernelEqForComparison: Eq[Comparison] = Eq.fromUniversalEquals +} diff --git a/kernel/src/main/scala/cats/kernel/Order.scala b/kernel/src/main/scala/cats/kernel/Order.scala index 052467961c..7ec2c13007 100644 --- a/kernel/src/main/scala/cats/kernel/Order.scala +++ b/kernel/src/main/scala/cats/kernel/Order.scala @@ -29,6 +29,17 @@ trait Order[@sp A] extends Any with PartialOrder[A] { self => */ def compare(x: A, y: A): Int + /** + * Like `compare`, but returns a [[cats.kernel.Comparison]] instead of an Int. + * Has the benefit of being able to pattern match on, but not as performant. + */ + def comparison(x: A, y: A): Comparison = { + val r = compare(x, y) + if (r > 0) Comparison.GreaterThan + else if (r == 0) Comparison.EqualTo + else Comparison.LessThan + } + def partialCompare(x: A, y: A): Double = compare(x, y).toDouble /**