2016-05-18 7 views
1

Я пытаюсь придумать комбинатор, который позволил бы мне сделать что-то вроде этого:адаптера для частично определённой функции

def pfAdapter(pf: PartialFunction[String, String]): PartialFunction[(String,String), String]) = { 
    case (a,b) if(pf.isDefinedAt(a)) => pf(a) 
} 

В принципе, у меня есть Map[String, String], и вы хотим использовать его в качестве PartialFunction, который принимает два параметры - игнорировать второй элемент и использовать первый в качестве ключа.

Подход выше работает, но мне не нравится тот факт, что pf по существу получает оценку дважды (возможно, нет никакого способа обойти это), и что это не «элегантно» ... Мне было интересно, есть ли какие-то вроде комбинатора, о котором я не знаю, позволил бы мне сделать что-то вроде { _._1 } andThen pf. Эта последняя попытка не работает, очевидно, потому что результат ее всегда определяется и будет просто терпеть неудачу на несуществующих ключах, я просто использую его в качестве иллюстрации того, как идеально будет выглядеть решение, которое я ищу.

Идеи, кто-нибудь?

+0

Итак, вы в порядке с исключением, если ключ не существует? Или я чего-то не хватает? –

+0

Нет, я хочу PartialFunction, который определен на кортежах, имея свой первый элемент в качестве ключа на карте. Нет исключений :) – Dima

+0

Тогда как это частично? Каков желаемый результат, когда первый элемент отсутствует? –

ответ

2

Сама функция (pf.apply) не реально оценивается в два раза, но его isDefinedAtявляется оценивается дважды для успешных матчей с вашим определением. И это означает, что дважды оценивается unapply -s и охранники в начальном PartialFunctionpf.

Кстати, в Скаласе есть комбинатор, который делает аналогичную вещь: 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 не оценить функции источника в два раза при каждом вызове
+0

Спасибо, что указали мне на '.unlift', я не знал об этом. – Dima

 Смежные вопросы

  • Нет связанных вопросов^_^