1

我正在尝试提出一个组合器,它可以让我做这样的事情:

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

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

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

想法,有人吗?

4

1 回答 1

2

函数本身 ( pf.apply) 并没有真正被评估两次,但它isDefinedAt 评估两次以成功匹配您的定义。这意味着unapply在初始时评估两次 -s 和警卫PartialFunction pf

顺便说一句,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,即:

对于所有部分函数文字,编译器会生成一个 applyOrElse 实现,以避免对模式匹配器和守卫进行双重评估。这使得 applyOrElse 成为许多操作和场景高效实现的基础,例如:

...

  • lift 和 unlift 不会在每次调用时两次评估源函数
于 2016-05-18T13:02:54.737 回答