Сама функция (pf.apply
) не реально оценивается в два раза, но его isDefinedAt
является оценивается дважды для успешных матчей с вашим определением. И это означает, что дважды оценивается unapply
-s и охранники в начальном PartialFunction
pf
.
Кстати, в Скаласе есть комбинатор, который делает аналогичную вещь: pf.first.andThen(_._1)
, но в основном это эквивалентно вашему определению.
Вы можете написать небольшой тест, чтобы увидеть, если pf.isDefinedAt
вычисляется дважды и запустить его с несколькими возможными реализациями 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))
и результат выполнения этого кода выглядит следующим образом:
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)
Таким образом, первая реализация pfAdapter
: Function.unlift((t: (A, T)) => pf.lift(t._1))
фактически делает избегает оценки isDefinedAt
дважды.
Это работает, потому что Map.collect
осуществляется с PartialFunction.applyOrElse
и документацию для applyOrElse
состояний, что:
Для всех частичных функций литералов компилятор генерирует реализацию applyOrElse, которая позволяет избежать двойной оценки модели matchers и охранники. Это делает applyOrElse основы для эффективной реализации для многих операций и сценариев, такие как:
...
- подъемника и unlift не оценить функции источника в два раза при каждом вызове
Итак, вы в порядке с исключением, если ключ не существует? Или я чего-то не хватает? –
Нет, я хочу PartialFunction, который определен на кортежах, имея свой первый элемент в качестве ключа на карте. Нет исключений :) – Dima
Тогда как это частично? Каков желаемый результат, когда первый элемент отсутствует? –