体验 Scala 高阶函数如何精简代码

本文来自 《Scala 编程》一书第九章第一节

高阶函数的好处之一是它们能让你创造控制抽象从而减少代码重复。

简化 API 代码

例如,假设你正在编写文件浏览器,并且想要提供 API,以便使用者搜索匹配某些标准的文件。首先我们写一个功能,让用户能根据后缀名搜索。

def filesHere = new java.io.File(".").listFiles()

def filesEnding(query: String) = {
for (file <- filesHere; if file.getName.endsWith(query)) yield file
}

效果似乎不错,然而有些用户想要的不仅仅是搜索某种特定文件类型,他们还想匹配文件名部分,于是你又加了一个函数

def filesHere = new java.io.File(".").listFiles()

def filesEnding(query: String) = {
for (file <- filesHere; if file.getName.endsWith(query)) yield file
}

def filesContains(query: String) = {
for (file <- filesHere; if file.getName.contains(query)) yield file
}

嗯,还行,虽然有点重复的代码

很快一些高级用户来了,他们会正则,所以希望加入正则匹配搜索,这对你来说太简单了,于是

def filesHere = new java.io.File(".").listFiles()

def filesEnding(query: String) = {
for (file <- filesHere; if file.getName.endsWith(query)) yield file
}

def filesContains(query: String) = {
for (file <- filesHere; if file.getName.contains(query)) yield file
}

def filesRegex(query: String) = {
for (file <- filesHere; if file.getName.matches(query)) yield file
}

你开始觉得不好了,代码重复太多,想想如果是 java,你打算怎么优化它?似乎不太容易,我们再来看看 scala 的方式。首先定义了一个fileMatching函数,接受一个匹配的字符串,和另一个以两个 String 作为输入,以 boolean 作为输出的函数,这样我们的3个 API 简单了不少

def filesHere = new java.io.File(".").listFiles()

def fileMatching(query: String, f: (String, String) => Boolean) = {
for (file <- filesHere; if f(file.getName, query)) yield file
}

def filesEnding(query: String) = fileMatching(query, _ endsWith _)

def filesContains(query: String) = fileMatching(query, _ contains _)

def filesRegex(query: String) = fileMatching(query, _ matches _)

等等,这个 query 似乎一直在被传递来传递去,是不是也可以优化?

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 filesContains(query: String) = filesMatching(_ contains query)

def filesRegex(query: String) = filesMatching(_ matches query)

到此3个 API 的可读性其实有了质的提升,filesEndingfilesContains, filesRegex分别以后缀匹配,名字包含,正则匹配进行过滤

简化客户端代码

前面我们看到了 高阶函数 简化 API 设计的能力,现在再来看如何简化客户端代码

客户端原来的代码是这样的

// list 是否包含负数
def containsNeg(nums: List[Int]) = {
var flag = false
for (num <- nums) if (num < 0) flag = true
flag
}

// list 是否包含奇数
def containsOdd(nums: List[Int]) = {
var flag = false
for (num <- nums) if (num % 2 == 1) flag = true
flag
}

然而 scala 在自身标准库中提供了 exists 高阶函数,所以在客户端代码中,只要传入业务逻辑即可

// exists 源码
def exists(p: A => Boolean): Boolean = {
var these = this
while (!these.isEmpty) {
if (p(these.head)) return true
these = these.tail
}
false
}

使用高阶函数后的客户端代码就成了

nums.exists(_ < 0)
nums.exists(_ %2 == 1)


- - - - - - - - End Thank For Your Reading - - - - - - - -