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

14

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已不足以确定我的函数在给定值处是否已定义。

例如,在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个回答

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,将不会生成任何匹配。


谢谢!那么自定义提取器就是正确的选择。不错的解决方案——尽管有点难以理解正在发生的事情... - Jean-Philippe Pellet
你可以写成 case Req(path, ext @ CheckExtension(valid), tpe) 的形式,以明确显示 Req 给你的内容。 - Debilski
哇!我从不知道你可以在模式提取器中使用替代方案。谢谢。 - Bill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接