Skip to content

Instantly share code, notes, and snippets.

Created January 28, 2011 06:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/799916 to your computer and use it in GitHub Desktop.
Save anonymous/799916 to your computer and use it in GitHub Desktop.
A rethink of PartialFunction.
package scala
/** A partial function of type `PartFunc[A, B]` is a
* unary function where the domain does not necessarily include all values of type
* `A`. The function `isDefinedAt` allows to
* test dynamically if a value is in the domain of the function.
*
* @author Martin Odersky
* @version 1.0, 16/07/2003
*/
trait PartFunc[-A, +B] extends (A => B) {
/** Checks if a value is contained in the function's domain.
*
* @param x the value to test
* @return `true`, iff `x` is in the domain of this function, `false` otherwise.
*/
def isDefinedAt(x: A): Boolean
def apply(x :A) = applyOrElse(x, (_ :A) => throw new MatchError)
def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 =
mapOrElse(x, identity[B1], default)
def mapOrElse[A1 <: A, B1 >: B, C](x :A1, f: B1 => C, default: A1 => C): C
def mapIf[A1 <: A, B1 >: B] (x :A1, f: B1 => Unit) :Unit = mapOrElse(x, f, (_: A1) => ())
/** Composes this partial function with a fallback partial function which gets applied where this
* partial function is not defined.
*
* @param that the fallback function
* @tparam A1 the argument type of the fallback function
* @tparam B1 the result type of the fallback function
* @return a partial function which has as domain the union of the domains
* of this partial function and `that`. The resulting partial function
* takes `x` to `this(x)` where `this` is defined, and to `that(x)` where it is not.
*/
def orElse[A1 <: A, B1 >: B](that: PartFunc[A1, B1]) : PartFunc[A1, B1] =
new PartFunc[A1, B1] {
def isDefinedAt(x: A1): Boolean =
PartFunc.this.isDefinedAt(x) || that.isDefinedAt(x)
def mapOrElse[A2 <: A1, B2 >: B1, C](x :A2, f: B2 => C, default: A2 => C): C =
PartFunc.this.mapOrElse(x, f, (v :A2) => that.mapOrElse(v, f, default))
}
/** Composes this partial function with a transformation function that gets applied
* to results of this partial function.
* @param k the transformation function
* @tparam C the result type of the transformation function.
* @return a partial function with the same domain as this partial function, which maps
* arguments `x` to `k(this(x))`.
*/
override def andThen[C](k: B => C) : PartFunc[A, C] = new PartFunc[A, C] {
def isDefinedAt(x: A): Boolean = PartFunc.this.isDefinedAt(x)
def mapOrElse[A1 <: A, C1 >: C, D](x :A1, f: C1 => D, default: A1 => D): D =
PartFunc.this.mapOrElse(x, f compose k, default)
}
/** Turns this partial function into an plain function returning an `Option` result.
* @return a function that takes an argument `x` to `Some(this(x))` if `this`
* is defined for `x`, and to `None` otherwise.
*/
def lift: A => Option[B] = { x => mapOrElse(x, (v :B) => Some(v), (_ :A) => None) }
}
/** A few handy operations which leverage the extra bit of information
* available in partial functions. Examples:
*
* <pre>
* import PartFunc._
*
* def strangeConditional(other: Any): Boolean = cond(other) {
* case x: String if x == "abc" || x == "def" => true
* case x: Int => true
* }
* def onlyInt(v: Any): Option[Int] = condOpt(v) { case x: Int => x }
* </pre>
*
* @author Paul Phillips
* @since 2.8
*/
object PartFunc
{
/** Creates a Boolean test based on a value and a partial function.
* It behaves like a 'match' statement with an implied 'case _ => false'
* following the supplied cases.
*
* @param x the value to test
* @param pf the partial function
* @return true, iff `x` is in the domain of `pf` and `pf(x) == true`.
*/
def cond[T](x: T)(pf: PartFunc[T, Boolean]): Boolean =
pf.mapOrElse(x, identity[Boolean], (_ :T) => false)
/** Transforms a PartFunc[T, U] `pf' into Function1[T, Option[U]] `f'
* whose result is Some(x) if the argument is in pf's domain and None otherwise,
* and applies it to the value `x'. In effect, it is a 'match' statement
* which wraps all case results in Some(_) and adds 'case _ => None' to the end.
*
* @param x the value to test
* @param pf the PartFunc[T, U]
* @return `Some(pf(x))` if `x` is in the domain of `pf`, `None` otherwise.
*/
def condOpt[T,U](x: T)(pf: PartFunc[T, U]): Option[U] =
pf.mapOrElse(x, (v :U) => Some(v), (_ :T) => None)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment