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
}
在这个例子中,它在很大程度上是可行的,但可读性降低了,而且我遇到过内联所有检查看起来非常难看的情况。
在这种情况下,您可能需要编写
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
,则没有生成匹配。