PartialFunction 的适配器

Adapter for a PartialFunction

我正在尝试想出一个允许我做这样的事情的组合器:

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

基本上,我有一个 Map[String, String],并想将其用作带有两个参数的 PartialFunction - 忽略第二个元素,并使用第一个作为键。

上面的方法有效,但我不喜欢 pf 本质上被评估两次(可能没有办法解决),而且它不是 "elegant" ...我想知道是否有某种我不知道的组合器可以让我做类似 { _._1 } andThen pf 的事情。显然,最后一次尝试不起作用,因为它的结果总是被定义的,并且只会在不存在的键上失败,我只是用它来说明我正在寻找的解决方案的理想外观。

想法,有人吗?

函数本身 (pf.apply) 并没有真正计算两次,但它的 isDefinedAt 计算两次以成功匹配您的定义。这意味着在初始 PartialFunction pf.

中评估两次 unapply-s 和守卫

顺便说一下,Scalaz 中有一个组合器可以做类似的事情: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 的文档指出:

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