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

com.stripe.brushfire.Predicate.scala Maven / Gradle / Ivy

package com.stripe.brushfire

import com.stripe.bonsai.Layout
import com.stripe.bonsai.layout.ProductLayout

/**
 * A `Predicate` is a function which accepts or rejects feature values.
 *
 * Given a value of type `V`, `apply()` will return a Boolean
 * indicating whether the predicate matches or not.
 *
 * There are six types of predicates:
 *
 *  - IsEq(c): x == c
 *  - NotEq(c): x != c
 *  - Lt(c): x < c
 *  - LtEq(c): x <= c
 *  - Gt(c): x > c
 *  - GtEq(c): x >= c
 *
 * Predicates can be negated using `!`, and can be transformed using
 * `map`. Evaluating a predicate requires an Ordering, but this
 * constraint is not enforced during construction, only when `apply()`
 * is invoked.
 */
sealed abstract class Predicate[V] extends Product with Serializable {

  import Predicate._

  /**
   * Returns the right-hand side of the predicate's comparison.
   */
  def value: V

  /**
   * Evaluate this predicate for the feature value `v`.
   */
  def apply(v: V)(implicit ord: Ordering[V]): Boolean =
    this match {
      case IsEq(x) => ord.equiv(v, x)
      case NotEq(x) => !ord.equiv(v, x)
      case Lt(x) => ord.lt(v, x)
      case LtEq(x) => ord.lteq(v, x)
      case Gt(x) => ord.gt(v, x)
      case GtEq(x) => ord.gteq(v, x)
    }

  /**
   * Negate this predicate.
   *
   * The resulting predicate will return true in cases where this
   * predicate returns false.
   */
  def unary_!(): Predicate[V] =
    this match {
      case IsEq(v) => NotEq(v)
      case NotEq(v) => IsEq(v)
      case Lt(v) => GtEq(v)
      case LtEq(v) => Gt(v)
      case Gt(v) => LtEq(v)
      case GtEq(v) => Lt(v)
    }

  /**
   * Map the value types of this [[Predicate]] using `f`.
   *
   * Remember that in order to evaluate a Predicate[U] you will need
   * to be able to provide a valid Ordering[U] instance.
   */
  def map[U](f: V => U): Predicate[U] =
    this match {
      case IsEq(v) => IsEq(f(v))
      case NotEq(v) => NotEq(f(v))
      case Lt(v) => Lt(f(v))
      case LtEq(v) => LtEq(f(v))
      case Gt(v) => Gt(f(v))
      case GtEq(v) => GtEq(f(v))
    }

  /**
   * Display this predicate, using the given feature name as a
   * placeholder.
   */
  def display(name: String): String =
    this match {
      case IsEq(v) => s"$name == $v"
      case NotEq(v) => s"$name != $v"
      case Lt(v) => s"$name < $v"
      case LtEq(v) => s"$name <= $v"
      case Gt(v) => s"$name > $v"
      case GtEq(v) => s"$name >= $v"
    }

  /**
   * Display this predicate as a string.
   */
  override def toString(): String =
    display("x")
}


object Predicate {

  // specific predicate types
  case class IsEq[V](value: V) extends Predicate[V]
  case class NotEq[V](value: V) extends Predicate[V]
  case class Lt[V](value: V) extends Predicate[V]
  case class LtEq[V](value: V) extends Predicate[V]
  case class Gt[V](value: V) extends Predicate[V]
  case class GtEq[V](value: V) extends Predicate[V]

  // predicate factory constructors, to help fix the correct return
  // type (Predicate[V]).
  def isEq[V](x: V): Predicate[V] = IsEq(x)
  def notEq[V](x: V): Predicate[V] = NotEq(x)
  def lt[V](x: V): Predicate[V] = Lt(x)
  def ltEq[V](x: V): Predicate[V] = LtEq(x)
  def gt[V](x: V): Predicate[V] = Gt(x)
  def gtEq[V](x: V): Predicate[V] = GtEq(x)

  private def unpack[V](pred: Predicate[V]): (Byte, V) =
    pred match {
      case IsEq(value) => (0.toByte, value)
      case NotEq(value) => (1.toByte, value)
      case Lt(value) => (2.toByte, value)
      case LtEq(value) => (3.toByte, value)
      case Gt(value) => (4.toByte, value)
      case GtEq(value) => (5.toByte, value)
    }

  private def pack[V](i: Byte, value: V): Predicate[V] =
    (i: @annotation.switch) match {
      case 0 => IsEq(value)
      case 1 => NotEq(value)
      case 2 => Lt(value)
      case 3 => LtEq(value)
      case 4 => Gt(value)
      case 5 => GtEq(value)
    }

  implicit def predicateLayout[V: Layout]: Layout[Predicate[V]] =
    new ProductLayout(Layout[Byte], Layout[V], unpack, pack)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy