所有的函数都被分割成通用部分,它们在每次函数调用中都相同,以及非通用部分,在不同的函数调用中可能会变化。通用部分是函数体,而非通用部分必须由参数提供。当你把函数值用做参数时,算法的非通用部分就是它代表的某些其它算法。在这种函数的每一次调用中,你都可以把不同的函数值作为参数传入,于是被调用函数将在每次选用参数的时候调用传入的函数值。这种高阶函数:higher-order function——带其它函数做参数的函数——给了你额外的机会去组织和简化代码。
高阶函数的一个好处是它们能让你创造控制抽象从而使你减少代码重复。例如,假设你正在写一个文件浏览器,并且你想要提供一个API,能够允许使用者搜索匹配某些标准的文件。首先,你加入了搜索文件名结束于特定字串的机制。这能让你的用户发现,比方说,所有扩展名为“.scala”的文件。你可以通过在单例对象中定义公开的filesEnding方法提供这样的API,如:
filesEnding方法通过使用私有帮助方法filesHere接受当前目录所有文件的列表,然后基于是否每个文件名以用户特定的查询结尾来过滤它们。由于filesHere是私有的,filesEnding方法是定义在你提供给你用户的API,FilesMatcher中唯一可以访问的方法。
目前为止还挺好,没有重复的代码。然而后来,你决定让别人可以基于文件名的任何部分做查询。这个功能可以良好地用于以下情况:你的用户记不住他们是以phb-important.doc,stupid-pub-report.doc,may2003salesdoc.phb,或什么完全不同的名字来命名文件的,但他们认为“phb”出现在文件的什么地方。你回到工作并把这个函数加到你的API,FileMatcher中:
这段函数与filesEnding很像。它搜索filesHere,检查名称,并且如果名称匹配则返回文件。唯一的差别是这个函数使用了contains替代endsWith。
随着时间的推移,程序变得更加成功。最后,你屈服于几个强势用户的需求,他们想要基于正则表达式搜索。这些马虎的家伙拥有数千个文件的超大目录,他们希望能做到像发现所有在题目中什么地方包含“oopsla”的“pdf”文件这样的事。为了支持他们,你写了这个函数:
有经验的程序员会注意到所有的这些重复并想知道是否能从中提炼出通用的帮助函数。然而,显而易见的方式不起作用。你希望能做的的是这样的:
这种方式在某些动态语言中能起作用,但Scala不允许在运行期这样粘合代码。那么你该做什么呢?
函数值提供了一个答案。虽然你不能把方法名当作值传递,但你可以通过传递为你调用方法的函数值达到同样的效果。在这个例子里,你可以给方法添加一个matcher参数,其唯一的目的就是针对查询检查文件名:
方法的这个版本中,if子句现在使用matcher针对查询检查文件名。更精确的说法是这个检查不依赖于matcher定义了什么。现在看一下matcher的类型。它是一个函数,因此类型中有个=>。这个函数带两个字串参数——文件名和查询——并返回布尔值,因此这个函数的类型是(String, String) => Boolean。
有了这个新的filesMatching帮助方法,你可以通过让三个搜索方法调用它,并传入合适的函数来简化它们: