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

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

The newest version!
package cats.kernel

import java.lang.Double.isNaN
import scala.{specialized => sp}
import compat.scalaVersionSpecific._

/**
 * The `PartialOrder` type class is used to define a partial ordering on some type `A`.
 *
 * A partial order is defined by a relation <=, which obeys the following laws:
 *
 * - x <= x (reflexivity)
 * - if x <= y and y <= x, then x = y (anti-symmetry)
 * - if x <= y and y <= z, then x <= z (transitivity)
 *
 * To compute both <= and >= at the same time, we use a Double number
 * to encode the result of the comparisons x <= y and x >= y.
 * The truth table is defined as follows:
 *
 * x <= y    x >= y      Double
 * true      true        = 0.0     (corresponds to x = y)
 * false     false       = NaN     (x and y cannot be compared)
 * true      false       = -1.0    (corresponds to x < y)
 * 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
   * sign is:
   * - negative iff `x < y`
   * - zero     iff `x = y`
   * - positive iff `x > y`
   */
  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]
   * where the Int sign is:
   * - negative iff `x < y`
   * - zero     iff `x = y`
   * - positive iff `x > y`
   */
  def tryCompare(x: A, y: A): Option[Int] = {
    val c = partialCompare(x, y).sign
    if (isNaN(c)) None else Some(c.toInt)
  }

  /**
   * Returns Some(x) if x <= y, Some(y) if x > y, otherwise None.
   */
  def pmin(x: A, y: A): Option[A] = {
    val c = partialCompare(x, y)
    if (c <= 0) Some(x)
    else if (c > 0) Some(y)
    else None
  }

  /**
   * Returns Some(x) if x >= y, Some(y) if x < y, otherwise None.
   */
  def pmax(x: A, y: A): Option[A] = {
    val c = partialCompare(x, y)
    if (c >= 0) Some(x)
    else if (c < 0) Some(y)
    else None
  }

  // The following may be overridden for performance:

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

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

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

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

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

abstract class PartialOrderFunctions[P[T] <: PartialOrder[T]] extends EqFunctions[P] {

  def partialCompare[@sp A](x: A, y: A)(implicit ev: P[A]): Double =
    ev.partialCompare(x, y)
  def tryCompare[@sp A](x: A, y: A)(implicit ev: P[A]): Option[Int] =
    ev.tryCompare(x, y)

  def pmin[@sp A](x: A, y: A)(implicit ev: P[A]): Option[A] =
    ev.pmin(x, y)
  def pmax[@sp A](x: A, y: A)(implicit ev: P[A]): Option[A] =
    ev.pmax(x, y)

  def lteqv[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean =
    ev.lteqv(x, y)
  def lt[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean =
    ev.lt(x, y)
  def gteqv[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean =
    ev.gteqv(x, y)
  def gt[@sp A](x: A, y: A)(implicit ev: P[A]): Boolean =
    ev.gt(x, y)
}

@suppressUnusedImportWarningForScalaVersionSpecific
object PartialOrder extends PartialOrderFunctions[PartialOrder] with PartialOrderToPartialOrderingConversion {

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

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

  /**
   * Defines a partial order on `A` from p where all arrows switch direction.
   */
  def reverse[@sp A](p: PartialOrder[A]): PartialOrder[A] =
    new PartialOrder[A] {
      def partialCompare(x: A, y: A): Double = p.partialCompare(y, x)
    }

  /**
   * Define a `PartialOrder[A]` using the given function `f`.
   */
  def from[@sp A](f: (A, A) => Double): PartialOrder[A] =
    new PartialOrder[A] {
      def partialCompare(x: A, y: A) = f(x, y)
    }

  def fromPartialOrdering[A](implicit ev: PartialOrdering[A]): PartialOrder[A] = new PartialOrder[A] {
    def partialCompare(x: A, y: A): Double =
      ev.tryCompare(x, y).fold(Double.NaN)(_.toDouble)
  }
}

trait PartialOrderToPartialOrderingConversion {
  implicit def catsKernelPartialOrderingForPartialOrder[A](implicit ev: PartialOrder[A]): PartialOrdering[A] =
    new PartialOrdering[A] {
      def tryCompare(x: A, y: A): Option[Int] = ev.tryCompare(x, y)
      def lteq(x: A, y: A): Boolean = ev.lteqv(x, y)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy