Skip to content

Commit

Permalink
Merge pull request #1102 from adelbertc/comparison
Browse files Browse the repository at this point in the history
Add {C,c}omparison to Order, fixes #1101
  • Loading branch information
non committed Jun 9, 2016
2 parents 8b2c42c + 50da459 commit 5cf9da3
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 5 deletions.
54 changes: 53 additions & 1 deletion kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,6 +79,58 @@ class LawTests extends FunSuite with Discipline {
laws[GroupLaws, (Int, Int)].check(_.band)

laws[GroupLaws, Unit].check(_.boundedSemilattice)

// Comparison related

// Something that can give NaN for test
def subsetPartialOrder[A]: PartialOrder[Set[A]] = new PartialOrder[Set[A]] {
def partialCompare(x: Set[A], y: Set[A]): Double =
if (x == y) 0.0
else if (x subsetOf y) -1.0
else if (y subsetOf x) 1.0
else Double.NaN
}

laws[OrderLaws, Set[Int]]("subset").check(_.partialOrder(subsetPartialOrder[Int]))

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("partialComparison") {
val po = subsetPartialOrder[Int]
val eqv = Eq[Option[Comparison]]
eqv.eqv(po.partialComparison(Set(1), Set()), Some(Comparison.GreaterThan)) &&
eqv.eqv(po.partialComparison(Set(), Set()), Some(Comparison.EqualTo)) &&
eqv.eqv(po.partialComparison(Set(), Set(1)), Some(Comparison.LessThan)) &&
eqv.eqv(po.partialComparison(Set(1, 2), Set(2, 3)), None)
}

test("signum . toInt . comparison = signum . compare") {
check { (i: Int, j: Int) =>
val found = Order[Int].comparison(i, j)
val expected = Order[Int].compare(i, j)
Eq[Int].eqv(found.toInt.signum, expected.signum)
}
}

test("signum . toDouble . partialComparison = signum . partialCompare") {
check { (x: Set[Int], y: Set[Int]) =>
val found = subsetPartialOrder[Int].partialComparison(x, y).map(_.toDouble.signum)
val expected = Some(subsetPartialOrder[Int].partialCompare(x, y)).filter(d => !d.isNaN).map(_.signum)
Eq[Option[Int]].eqv(found, expected)
}
}

// esoteric machinery follows...

implicit lazy val band: Band[(Int, Int)] =
Expand Down
30 changes: 30 additions & 0 deletions kernel/src/main/scala/cats/kernel/Comparison.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cats.kernel

/** ADT encoding the possible results of a comparison */
sealed abstract class Comparison(val toInt: Int, val toDouble: Double) extends Product with Serializable

object Comparison {
final case object GreaterThan extends Comparison(1, 1.0)
final case object EqualTo extends Comparison(0, 0.0)
final case object LessThan extends Comparison(-1, -1.0)

// Used for fromDouble
private val SomeGt = Some(Comparison.GreaterThan)
private val SomeEq = Some(Comparison.EqualTo)
private val SomeLt = Some(Comparison.LessThan)

def fromInt(int: Int): Comparison = {
if (int > 0) Comparison.GreaterThan
else if (int == 0) Comparison.EqualTo
else Comparison.LessThan
}

def fromDouble(double: Double): Option[Comparison] = {
if (double.isNaN) None
else if (double > 0.0) SomeGt
else if (double == 0.0) SomeEq
else SomeLt
}

implicit val catsKernelEqForComparison: Eq[Comparison] = Eq.fromUniversalEquals
}
8 changes: 6 additions & 2 deletions kernel/src/main/scala/cats/kernel/Order.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import scala.{specialized => sp}
* By the totality law, x <= y and y <= x cannot be both false.
*/
trait Order[@sp A] extends Any with PartialOrder[A] { self =>

/**
* Result of comparing `x` with `y`. Returns an Int whose sign is:
* - negative iff `x < y`
Expand All @@ -29,6 +28,12 @@ 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 = Comparison.fromInt(compare(x, y))

def partialCompare(x: A, y: A): Double = compare(x, y).toDouble

/**
Expand Down Expand Up @@ -135,7 +140,6 @@ abstract class OrderFunctions[O[T] <: Order[T]] extends PartialOrderFunctions[O]
}

object Order extends OrderFunctions[Order] {

/**
* Access an implicit `Order[A]`.
*/
Expand Down
9 changes: 7 additions & 2 deletions kernel/src/main/scala/cats/kernel/PartialOrder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import scala.{specialized => sp}
* false true = 1.0 (corresponds to x > y)
*/
trait PartialOrder[@sp A] extends Any with Eq[A] { self =>

/**
* Result of comparing `x` with `y`. Returns NaN if operands are not
* comparable. If operands are comparable, returns a Double whose
Expand All @@ -34,6 +33,13 @@ trait PartialOrder[@sp A] extends Any with Eq[A] { self =>
*/
def partialCompare(x: A, y: A): Double

/**
* Like `partialCompare`, but returns a [[cats.kernel.Comparison]] instead of an Double.
* Has the benefit of being able to pattern match on, but not as performant.
*/
def partialComparison(x: A, y: A): Option[Comparison] =
Comparison.fromDouble(partialCompare(x, y))

/**
* Result of comparing `x` with `y`. Returns None if operands are
* not comparable. If operands are comparable, returns Some[Int]
Expand Down Expand Up @@ -137,7 +143,6 @@ abstract class PartialOrderFunctions[P[T] <: PartialOrder[T]] extends EqFunction
}

object PartialOrder extends PartialOrderFunctions[PartialOrder] {

/**
* Access an implicit `PartialOrder[A]`.
*/
Expand Down

0 comments on commit 5cf9da3

Please sign in to comment.