All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cats.kernel.Order.scala Maven / Gradle / Ivy

The newest version!
package cats.kernel

import scala.{specialized => sp}

/**
 * The `Order` type class is used to define a total ordering on some type `A`.
 * An order is defined by a relation <=, which obeys the following laws:
 *
 * - either x <= y or y <= x (totality)
 * - if x <= y and y <= x, then x == y (antisymmetry)
 * - if x <= y and y <= z, then x <= z (transitivity)
 *
 * The truth table for compare is defined as follows:
 *
 * x <= y    x >= y      Int
 * true      true        = 0     (corresponds to x == y)
 * true      false       < 0     (corresponds to x < y)
 * false     true        > 0     (corresponds to x > y)
 *
 * 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`
   * - zero     iff `x = y`
   * - positive iff `x > y`
   */
  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

  /**
   * If x < y, return x, else return y.
   */
  def min(x: A, y: A): A = if (lt(x, y)) x else y

  /**
   * If x > y, return x, else return y.
   */
  def max(x: A, y: A): A = if (gt(x, y)) x else y

  // The following may be overridden for performance:

  /**
   * Returns true if `x` = `y`, false otherwise.
   */
  override def eqv(x: A, y: A): Boolean =
    compare(x, y) == 0

  /**
   * Returns true if `x` != `y`, false otherwise.
   *
   * Note: this default implementation provided by [[Order]] is the same as the
   * one defined in [[Eq]], but for purposes of binary compatibility, the
   * override in [[Order]] has not yet been removed.
   * See [[https://github.com/typelevel/cats/pull/2230#issuecomment-381818633 this discussion]].
   */
  override def neqv(x: A, y: A): Boolean = !eqv(x, y)

  /**
   * Returns true if `x` <= `y`, false otherwise.
   */
  override def lteqv(x: A, y: A): Boolean =
    compare(x, y) <= 0

  /**
   * Returns true if `x` < `y`, false otherwise.
   */
  override def lt(x: A, y: A): Boolean =
    compare(x, y) < 0

  /**
   * Returns true if `x` >= `y`, false otherwise.
   */
  override def gteqv(x: A, y: A): Boolean =
    compare(x, y) >= 0

  /**
   * Returns true if `x` > `y`, false otherwise.
   */
  override def gt(x: A, y: A): Boolean =
    compare(x, y) > 0

  /**
   * Convert a `Order[A]` to a `scala.math.Ordering[A]`
   * instance.
   */
  def toOrdering: Ordering[A] = new Ordering[A] {
    def compare(x: A, y: A): Int = self.compare(x, y)
  }
}

abstract class OrderFunctions[O[T] <: Order[T]] extends PartialOrderFunctions[O] {

  def compare[@sp A](x: A, y: A)(implicit ev: O[A]): Int =
    ev.compare(x, y)

  def min[@sp A](x: A, y: A)(implicit ev: O[A]): A =
    ev.min(x, y)

  def max[@sp A](x: A, y: A)(implicit ev: O[A]): A =
    ev.max(x, y)

  def comparison[@sp A](x: A, y: A)(implicit ev: O[A]): Comparison =
    ev.comparison(x, y)
}

trait OrderToOrderingConversion {

  /**
   * Implicitly derive a `scala.math.Ordering[A]` from a `Order[A]`
   * instance.
   */
  implicit def catsKernelOrderingForOrder[A](implicit ev: Order[A]): Ordering[A] =
    ev.toOrdering

}

object Order extends OrderFunctions[Order] with OrderToOrderingConversion {

  /**
   * Access an implicit `Order[A]`.
   */
  @inline final def apply[A](implicit ev: Order[A]): Order[A] = ev

  /**
   * Convert an implicit `Order[B]` to an `Order[A]` using the given
   * function `f`.
   */
  def by[@sp A, @sp B](f: A => B)(implicit ev: Order[B]): Order[A] =
    new Order[A] {
      def compare(x: A, y: A): Int = ev.compare(f(x), f(y))
    }

  /**
   * Defines an ordering on `A` from the given order such that all arrows switch direction.
   */
  def reverse[@sp A](order: Order[A]): Order[A] =
    new Order[A] {
      def compare(x: A, y: A): Int = order.compare(y, x)
    }

  /**
   * Returns a new `Order[A]` instance that first compares by the first
   * `Order` instance and uses the second `Order` instance to "break ties".
   *
   * That is, `Order.whenEqual(x, y)` creates an `Order` that first orders by `x` and
   * then (if two elements are equal) falls back to `y` for the comparison.
   */
  def whenEqual[@sp A](first: Order[A], second: Order[A]): Order[A] =
    new Order[A] {
      def compare(x: A, y: A) = {
        val c = first.compare(x, y)
        if (c == 0) second.compare(x, y)
        else c
      }
    }

  /**
   * Define an `Order[A]` using the given function `f`.
   */
  def from[@sp A](f: (A, A) => Int): Order[A] =
    new Order[A] {
      def compare(x: A, y: A) = f(x, y)
    }

  /**
   * Define an `Order[A]` using the given 'less than' function `f`.
   */
  def fromLessThan[@sp A](f: (A, A) => Boolean): Order[A] =
    new Order[A] {
      override def compare(x: A, y: A): Int =
        if (f(x, y)) -1 else if (f(y, x)) 1 else 0

      // Overridden for performance (avoids multiple comparisons)
      override def eqv(x: A, y: A): Boolean = !(f(x, y) || f(y, x))
      override def neqv(x: A, y: A): Boolean = f(x, y) || f(y, x)
      override def lteqv(x: A, y: A): Boolean = !f(y, x)
      override def lt(x: A, y: A): Boolean = f(x, y)
      override def gteqv(x: A, y: A): Boolean = !f(x, y)
      override def gt(x: A, y: A): Boolean = f(y, x)
    }

  /**
   * An `Order` instance that considers all `A` instances to be equal.
   */
  def allEqual[A]: Order[A] =
    new Order[A] {
      def compare(x: A, y: A): Int = 0
    }

  /**
   * A `Monoid[Order[A]]` can be generated for all `A` with the following
   * properties:
   *
   * `empty` returns a trivial `Order[A]` which considers all `A` instances to
   * be equal.
   *
   * `combine(x: Order[A], y: Order[A])` creates an `Order[A]` that first
   * orders by `x` and then (if two elements are equal) falls back to `y`.
   *
   * This monoid is also a `Band[Order[A]]` since its combine
   * operations is idempotent.
   *
   * @see [[Order.whenEqual]]
   */
  def whenEqualMonoid[A]: Monoid[Order[A]] with Band[Order[A]] =
    new Monoid[Order[A]] with Band[Order[A]] {
      val empty: Order[A] = allEqual[A]
      def combine(x: Order[A], y: Order[A]): Order[A] = Order.whenEqual(x, y)
    }

  def fromOrdering[A](implicit ev: Ordering[A]): Order[A] =
    new Order[A] {
      def compare(x: A, y: A): Int = ev.compare(x, y)

      override def toOrdering: Ordering[A] = ev
    }

  def fromComparable[A <: Comparable[A]]: Order[A] =
    new Order[A] {
      override def compare(x: A, y: A): Int =
        x.compareTo(y)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy