It is quite possible that to know whether a function is defined at some point, a significant part of computing its value has to be done. In a PartialFunction
, when implementing isDefined
and apply
, both methods will have to do that. What to do is this common job is costly?
There is the possibility of caching its result, hoping that apply will be called after isDefined. Definitely ugly.
I often wish that PartialFunction[A,B]
would be Function[A, Option[B]]
, which is clearly isomorphic. Or maybe, there could be another method in PartialFunction
, say applyOption(a: A): Option[B]
. With some mixins, implementors would have a choice of implementing either isDefined and apply or applyOption. Or all of them to be on the safe side, performance wise. Clients which test isDefined
just before calling apply would be encouraged to use applyOption
instead.
However, this is not so. Some major methods in the library, among them collect
in collections require a PartialFunction
. Is there a clean (or not so clean) way to avoid paying for computations repeated between isDefined and apply?
Also, is the applyOption(a: A): Option[B]
method reasonable? Does it sound feasible to add it in a future version? Would it be worth it?
Why is caching such a problem? In most cases, you have a local computation, so as long as you write a wrapper for the caching, you needn't worry about it. I have the following code in my utility library:
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)
and then if I have an expensive computation, I write a function method A => Option[B]
and do something like (f _).drop
to use it in collect or whatnot. (If you wanted to do it inline, you could create a method that takes A=>Option[B]
and returns a partial function.)
(The opposite transformation--from PartialFunction
to A => Option[B]
--is called lifting, hence the "drop"; "unlift" is, I think, a more widely used term for the opposite operation.)
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