在前面的文章中,我们说过Scala没有内置很多控制结构,这是因为Scala赋予了程序员自己扩展控制结构的能力。Scala支持函数值(值的类型为函数,而非函数的返回值),为避免混淆,我们使用函数类型值来指代类型为函数的值。
所有的函数可以分成两个部分:一是共有部分,这部分在该函数的调用都是相同的,另外一部分为分公共部分,这部分在每次调用该函数上是可以不同的。公共部分为函数的定义体,非公共部分为函数的参数。但你使用函数类型值做为另外一个函数的参数时,函数的非公共部分本身也是一个算法(函数),调用该函数时,每次你都可以传入不同函数类型值作为参数,这个函数称为高阶函数?函数的参数也可以是另外一个函数。
使用高级函数可以帮助你简化代码,它支持创建一个新的程序控制结构来减低代码重复。
比如,你打算写一个文件浏览器,你需要写一个API支持搜索给定条件的文件。首先,你添加一个方法,该方法可以通过查询包含给定字符串的文件,比如你可以查所有?.scala?结尾的文件。你可以定义如下的API:
object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesEnding(query : String) = for (file <-filesHere; if file.getName.endsWith(query)) yield file }
filesEnding 方法从本地目录获取所有文件(方法filesHere),然后使用过滤条件(文件以给定字符串结尾)输出给定条件的文件。
到目前为止,这代码实现非常好也没有什么重复的代码。后来,你有需要使用新的过滤条件,文件名包含指定字符串,而不仅仅以某个字符串结尾的文件列表。你有实现了下面的API。
def filesContaining( query:String ) = for (file <-filesHere; if file.getName.contains(query)) yield file
filesContaining和filesEnding 的实现非常类似,不同点在于一个使用endsWith,另一个使用contains函数调用。有过了一段时间,你有想支持使用正则表达式来查询文件,你有实现了下面的对象方法:
def filesRegex( query:String) = for (file <-filesHere; if file.getName.matches(query)) yield file
这三个函数的算法非常类似,所不同的是过滤条件稍有不同,在Scala中我们可以定义一个高阶函数,将这三个不同过滤条件抽象称一个函数作为参数传给搜索算法,我们可以定义这个高阶函数如下:
def filesMatching( query:String, matcher: (String,String) => Boolean) = { for(file <- filesHere; if matcher(file.getName,query)) yield file }
这个函数的第二个参数matcher的类型也为函数(如果你熟悉C#,类似于delegate),该函数的类型为 (String,String ) =>Boolean,可以匹配任意使用两个String类型参数,返回值类型为Boolean的函数。使用这个辅助函数,我们可以重新定义filesEnding,filesContaining和filesRegex。
def filesEnding(query:String) = filesMatching(query,_.endsWith(_)) def filesContaining(query:String)= filesMatching(query,_.contains(_)) def filesRegex(query:String) = filesMatching(query,_.matches(_))
这个新的实现和之前的实现已经简化了不少,实际上代码还可以简化,我们注意到参数query在filesMatching的作用只是把它传递给matcher参数,这种参数传递实际也是无需的,简化后代码如下:
object FileMatcher { private def filesHere = (new java.io.File(".")).listFiles def filesMatching( matcher: (String) => Boolean) = { for(file <- filesHere; if matcher(file.getName)) yield file } def filesEnding(query:String) = filesMatching(_.endsWith(query)) def filesContaining(query:String)= filesMatching(_.contains(query)) def filesRegex(query:String) = filesMatching(_.matches(query)) }
函数类型参数 _.endsWith(query),_.contains(query)和_.matches(query) 为函数闭包,因为它们绑定了一个自由变量query,因此我们可以看到闭包也可以用来简化代码。