Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inverse of PartialFunction's lift method

Tags:

scala

PartialFunction's lift method turns a PartialFunction into a Function returning an Option result.

Is there an inverse operation to this, that turns a Function1[A, Option[B]] into a PartialFunction[A, B]?

like image 282
kassens Avatar asked May 05 '11 18:05

kassens


4 Answers

It's hard to top all these fine answers from a range of scala luminaries, but in case you would like to know about the one in the standard library, it's in the scala.Function companion object. (In 2.9.)

/** Turns a function `A => Option[B]` into a `PartialFunction[A, B]`.  Important note:
 *  this transformation implies the original function will be called 2 or more
 *  times on each logical invocation, because the only way to supply an implementation
 *  of isDefinedAt is to call the function and examine the return value.
 *
 *  @param   f    a function T => Option[R]
 *  @return       a partial function defined for those inputs where
 *                f returns Some(_) and undefined where f returns None.
 *  @see PartialFunction#lift
 */
def unlift[T, R](f: T => Option[R]): PartialFunction[T, R] = new PartialFunction[T, R] {
  def apply(x: T): R = f(x).get
  def isDefinedAt(x: T): Boolean = f(x).isDefined
  override def lift: T => Option[R] = f
}
like image 113
psp Avatar answered Nov 09 '22 17:11

psp


Not in the library, but it's easy to build. However, isDefinedAt will have to fully evaluate the function making it more expensive than is typical for partial functions built from pattern matching and also possibly result in unwanted side effects.

scala> def unlift[A, B](f : (A => Option[B])) = new PartialFunction[A,B] {
     |    def isDefinedAt(x : A) = f(x).isDefined
     |    def apply(x : A) = f(x).get
     | }
unlift: [A,B](f: (A) => Option[B])java.lang.Object with PartialFunction[A,B]
scala> def f(x : Int) = if (x == 1) Some(1) else None
f: (x: Int)Option[Int]
scala> val g = unlift(f)
g: java.lang.Object with PartialFunction[Int,Int] = <function1>
scala> g.isDefinedAt(1)
res0: Boolean = true
scala> g.isDefinedAt(2)
res1: Boolean = false
scala> g(1)
res2: Int = 1
scala> g(2)
java.util.NoSuchElementException: None.get
    at scala.None$.get(Option.scala:262)
    at scala.None$.get(Option.scala:260)
    at $anon$1.apply(<console>:7)
    at scala.Function1$class.apply$mcII$sp(Function1.scala:39)
    at $anon$1.apply$mcII$sp(<console>:5)
    at .<init>(<console>:9)
    at .<clinit>(<console>)
    at RequestResult$.<init>(<console>:9)
    at RequestResult$.<clinit>(<console>)
    at RequestResult$scala_repl_result(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.scala:988)
    at scala.tools....

A purist might also wrap isDefinedAt with a try/catch block to return false on exceptions.

like image 41
James Iry Avatar answered Nov 09 '22 16:11

James Iry


To build on James' answer with a more complex example, I have the following code in my library of things-the-Scala-library-forgot (or didn't-trust-you-with):

class DroppedFunction[-A,+B](f: A => Option[B]) extends PartialFunction[A,B] {
  private[this] var tested = false
  private[this] var arg: A = _
  private[this] var ans: Option[B] = None
  private[this] def cache(a: A) {
    if (!tested || a != arg) {
      tested = true
      arg = a
      ans = f(a)
    }
  }        
  def isDefinedAt(a: A) = {
    cache(a)
    ans.isDefined
  }
  def apply(a: A) = {
    cache(a)
    ans.get
  }
}
class DroppableFunction[A,B](f: A => Option[B]) {
  def drop = new DroppedFunction(f)
}
implicit def function_is_droppable[A,B](f: A => Option[B]) = new DroppableFunction(f)

Most of the code is devoted to making sure the function evaluation is cached (as long as the apply comes right after the isDefinedAt). Example of use:

scala> val f = (x: Int) => if (x>=0) Some(x) else None
f: (Int) => Option[Int] = <function1>

scala> Array(-2,-1,0,1,2).collect(f.drop)
res0: Array[Int] = Array(0, 1, 2)

The caching helps speed things up and avoid double-side-effect problems (at least when isDefinedAt is used immediately before apply, and when the function omits side effects when it returns None).

like image 6
Rex Kerr Avatar answered Nov 09 '22 16:11

Rex Kerr


You can use Function.unlift, See docs.

like image 2
Shani Elharrar Avatar answered Nov 09 '22 17:11

Shani Elharrar