The function itself (pf.apply
) is not really evaluated twice, but its isDefinedAt
is evaluated twice for successful matches with your definition. And that means evaluating twice unapply
-s and guards in the initial PartialFunction
pf
.
By the way there is a combinator in Scalaz that does a similar thing: pf.first.andThen(_._1)
, but it is basically equivalent to your definition.
You can write a small test, to see if pf.isDefinedAt
is evaluated twice and run it with several possible implementations of pfAdapter
:
object Unapply {
def unapply(s: String): Boolean = {
println(s"unapplying on $s")
s == "1"
}
}
val m = Map("1" -> 1, "2" -> 2)
def pf: PartialFunction[String, String] = {
case Unapply() => "11"
}
def pfAdapter1[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] =
Function.unlift((t: (A, T)) => pf.lift(t._1))
def pfAdapter2[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] =
new PartialFunction[(A, T), B] {
def isDefinedAt(arg: (A, T)) = pf.isDefinedAt(arg._1)
def apply(arg: (A, T)) = pf(arg._1)
}
def pfAdapter3[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] = {
case (a,b) if pf.isDefinedAt(a) => pf(a)
}
def pfAdapter4[A, B, T](pf: PartialFunction[A, B]): PartialFunction[(A, T), B] = {
import scalaz.Scalaz._
pf.first.andThen(_._1)
}
println(m collect pfAdapter1(pf))
println(m collect pfAdapter2(pf))
println(m collect pfAdapter3(pf))
println(m collect pfAdapter4(pf))
And the result of executing this code is as follows:
unapplying on 1
unapplying on 2
List(11)
unapplying on 1
unapplying on 1
unapplying on 2
List(11)
unapplying on 1
unapplying on 1
unapplying on 2
List(11)
unapplying on 1
unapplying on 1
unapplying on 2
List(11)
So the first implementation of pfAdapter
: Function.unlift((t: (A, T)) => pf.lift(t._1))
actually does avoid evaluating isDefinedAt
twice.
This works, because Map.collect
is implemented with PartialFunction.applyOrElse
, and the documentation for applyOrElse
states, that:
For all partial function literals the compiler generates an
applyOrElse implementation which avoids double evaluation of pattern
matchers and guards. This makes applyOrElse the basis for the
efficient implementation for many operations and scenarios, such as:
...
- lift and unlift do not evaluate source functions twice on each invocation