如何在Scala中轻松定义更复杂的PartialFunction?

时间:2011-07-07 10:46:19

标签: scala lift partialfunction

PartialFunctions

在Scala中,简而言之,PartialFunction是一个额外定义isDefinedAt方法的函数。

使用一系列case语句定义部分函数很容易。一个简单的例子就是,例如:

scala> val pf: PartialFunction[Int, Unit] = {
     | case 42 => ()
     | }
pf: PartialFunction[Int,Unit] = <function1>

scala> pf.isDefinedAt(42)
res0: Boolean = true

scala> pf.isDefinedAt(0) 
res1: Boolean = false

isDefinedAt是从定义部分函数的case列表中自动生成的。

上下文

Lift框架在许多地方使用部分功能,例如定义一个请求是应该由Lift的引擎处理还是直接从磁盘上的文件提供服务。有时,我发现自己想要编写一个匹配所有输入参数的case语句,然后才决定是否要返回一个值。这意味着case s的初始序列不再足以确定我的函数是否定义为给定值

例如,在Lift中,我想添加一条规则,直接提供所有html和htm文件,并且应该处理带有“lift”扩展名的文件。做这样的事情会很容易:

LiftRules.liftRequest.prepend {
  case Req(path, extension, tpe) => extension match {
    case "html" | "htm" => false
    case "lift" => true
  }
}

不幸的是,在这种情况下,编译器认为我的部分函数是在任何地方定义的,因为第一个case始终匹配。它是嵌套的match,可能与所有传入的请求都不匹配。并且,如果请求不匹配,则会抛出MatchError

问题

在定义部分函数时,是否有一种简单的方法可以让编译器考虑嵌套的match语句,或者是这样做的唯一方法是内联所有这样的嵌套条件?

LiftRules.liftRequest.prepend {
  case Req(path, extension, tpe) if extension == "html" || extension == "htm" => false
  case Req(path, extension, tpe) if extension == "lift" => true
}

在这个例子中,它在很大程度上是可行的,但是可读性降低了,而且我遇到了内联所有检查看起来非常难看的情况。

1 个答案:

答案 0 :(得分:23)

在这种情况下,您可能想要写

LiftRules.liftRequest.prepend {
  case Req(path, "html" | "htm", tpe) => false
  case Req(path, "lift", tpe) => true
}

对于更复杂的情况,您需要定义自己的提取器,而不是嵌套的case语句。

object CheckExtension {
  def unapply(ext: String) = ext match {
    case "lift" => Some(true)
    case "html" | "htm" => Some(false)
    case _ => None
  }
}

LiftRules.liftRequest.prepend {
  case Req(path, CheckExtension(valid), tpe) => valid
}

只有在预定义的unapply函数返回Some并将Some的值分配给自由变量valid时,才会匹配。如果unapply返回None,则不会生成匹配项。