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]
?
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
}
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.
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
).
You can use Function.unlift
, See docs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With