Подтвердить что ты не робот

Конструкция PartialFunction неэффективна?

Это то, о чем я задумывался некоторое время. Я вижу этот шаблон много:

if (pf.isDefinedAt(in)) pf(in)

Разбирая это на два отдельных вызова, все шаблоны, которые были оценены в #isDefinedAt, также оцениваются в #apply. Например:

object Ex1 {
  def unapply(in: Int) : Option[String] = {
    println("Ex1")
    if (in == 1) Some("1") else None
  }
}

object Ex2 {
  def unapply(in: Int) : Option[String] = {
    println("Ex2")
    if (in == 2) Some("2") else None
  }
}

val pf : PartialFunction[Int,String] = {
  case Ex1(result) => result
  case Ex2(result) => result
}

val in = 2

if (pf.isDefinedAt(in)) pf(in)

Какая печать

Ex1
Ex2
Ex1
Ex2
res52: Any = 2

В худшем случае, когда ваш шаблон соответствует последнему, вы дважды оценивали ваши шаблоны/экстракторы при вызове PartialFunction. Это может стать неэффективным при сопоставлении с пользовательскими экстракторами, которые сделали больше, чем просто сопоставление классов или списков (например, если у вас был экстрактор, который разбирал XML-документ и возвращал некоторые объекты значений)

Линейка PartialFunction # имеет такую ​​же двойную оценку:

scala> pf.lift(2)
Ex1
Ex2
Ex1
Ex2
res55: Option[String] = Some(2)

Есть ли способ условно вызывать функцию, если она определена без потенциального вызова всех ваших экстракторов дважды?

4b9b3361

Ответ 1

Существует беседа об этом прямо сейчас в списке рассылки scala -internals. Мартин Одерский предложил новый тип: FunctionWithDefault. Мартин говорит не только о временном штрафе, но и о штрафе за компиляцию (от взлома класса) при использовании PartialFunction:

Сначала нам нужно сгенерировать код соответствия шаблонов дважды, один раз в приложении, а затем снова в isDefinedAt. Во-вторых, нам также нужно выполнить код дважды, сначала проверить, применима ли эта функция, а затем применить ее.

Ответ на ваш вопрос по существу "да", и это поведение (из PartialFunction) не изменится ни из-за проблем с обратной совместимостью (например, что, если isDefinedAt является побочным эффектом).

Предлагаемый новый тип FunctionWithDefault не имеет isDefinedAt и имеет способ:

trait FunctionWithDefault[-I, +O] {
  def applyOrElse[OO >: O](i : I, default : I => OO) : OO
}

который действует как метод Option getOrElse.

Я должен сказать, что, как обычно, я не могу представить, что эта неэффективность представляет собой проблему с производительностью в подавляющем большинстве случаев.