дорогостоящие вычисления, происходящие как в isDefined, так и в Apply функции PartialFunction - PullRequest
8 голосов
/ 19 августа 2011

Вполне возможно, что для того, чтобы узнать, определена ли функция в какой-то момент, необходимо выполнить значительную часть вычисления ее значения. В PartialFunction при реализации isDefined и apply оба метода должны будут это сделать. Что делать, если эта обычная работа дорогая?

Существует возможность кэширования его результата, надеясь, что apply будет вызвано после isDefined. Определенно некрасиво.

Мне часто хочется, чтобы PartialFunction[A,B] было бы Function[A, Option[B]], что явно изоморфно. Или, может быть, в PartialFunction может быть другой метод, скажем applyOption(a: A): Option[B]. С некоторыми миксинами у разработчиков будет выбор реализации isDefined и apply или applyOption. Или все они, чтобы быть на безопасной стороне, с точки зрения производительности. Клиентам, которые проверяют isDefined непосредственно перед вызовом, будет рекомендовано использовать applyOption.

Однако это не так. Некоторые основные методы в библиотеке, среди которых collect в коллекциях, требуют PartialFunction. Есть ли чистый (или не очень чистый) способ избежать оплаты вычислений, повторяемых между isDefined и apply?

Кроме того, разумен ли метод applyOption(a: A): Option[B]? Возможно ли добавить его в будущей версии? Стоит ли это того?

Ответы [ 4 ]

4 голосов
/ 19 августа 2011

Почему кеширование такая проблема? В большинстве случаев у вас есть локальные вычисления, поэтому, пока вы пишете оболочку для кэширования, вам не нужно об этом беспокоиться. В моей служебной библиотеке есть следующий код:

  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)

и затем, если у меня есть дорогостоящие вычисления, я пишу метод функции A => Option[B] и делаю что-то вроде (f _).drop, чтобы использовать его в методе сбора или еще чего-нибудь. (Если вы хотите сделать это встроенным, вы можете создать метод, который принимает A=>Option[B] и возвращает частичную функцию.)

(Обратное преобразование - от PartialFunction до A => Option[B] - называется подъемом, следовательно, «падение»; я думаю, что «отмена» является более широко используемым термином для противоположной операции.)

3 голосов
/ 19 августа 2011

Посмотрите на эту тему, Переосмысление PartialFunction .Вы не единственный, кто интересуется этим.

2 голосов
/ 19 августа 2011

Это интересный вопрос, и я дам свои 2 цента.

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

Теперь, если предположить, что есть проблема, откуда она взялась?

  1. Может быть большое количество случаев
  2. Сложное сопоставление с образцом
  3. Некоторые сложные вычисления по причинам if

Один из вариантов, который я бы попытался найти, чтобы быстро потерпеть неудачу.Разбейте сопоставление с образцом на слой, затем объедините частичные функции.Таким образом, вы можете провалить матч рано.Также извлеките повторное совпадение.Например:

Предположим, OddEvenList - это экстрактор, который разбивает список на нечетный и четный список:

var pf1: PartialFuntion[List[Int],R] = { 
   case OddEvenList(1::ors, 2::ers) =>
   case OddEvenList(3::ors, 4::ors) => 
}

Разбить на две части, одну, которая соответствует разбиению, затем ту, котораяпытается сопоставить результат (чтобы избежать повторных вычислений. Однако это может потребовать некоторой реинжиниринга

var pf2: PartialFunction[(List[Int],List[Int],R) = {
   case (1 :: ors, 2 :: ers) => R1
   case (3 :: ors, 4 :: ors) => R2
}
var pf1: PartialFuntion[List[Int],R] = { 
   case OddEvenList(ors, ers) if(pf2.isDefinedAt(ors,ers) => pf2(ors,ers)
}

. Я использовал это при последовательном чтении файлов XML, которые имеют довольно непостоянный формат.

Другой вариант - составить частичные функции, используя andThen. Хотя быстрый тест, показанный здесь, показывает, что только первый из них - это тесты.

0 голосов
/ 19 августа 2011

Нет абсолютно ничего плохого в механизме кэширования внутри частичной функции, если:

  1. функция возвращает всегда один и тот же ввод, когда передается один и тот же аргумент
  2. , она не имеет стороныэффекты
  3. полностью скрыты от остального мира

Такая кэшированная функция не отличается от простой старой чистой частичной функции ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...