转载

函数之趣

作者:Olivier Halligon, 原文链接 ,原文日期:2015/08/28

译者: 小锅 ;校对: numbbbbb ;定稿: shanks

今天我们要研究 Swift 函数中一些好玩的特性,比如返回函数的函数,柯里化以及运算符函数。

函数的基础

在这篇文章中,我们将使用一个返回 Bool 类型的函数来判断它的参数是否符合某种特定的标准,这个函数接收一个 Int 类型的参数。这种类型的函数可以用来对保存整数类型的数组进行筛选。

让我们从最简单的开始,下面是一个用来判断它的参数是否为正数的函数:

func isPositive(value: Int) -> Bool {
return value > 0
}

[-4,-2,0,4,7,-8,3].filter(isPositive)
// returns: [4,7,3]

更通用的函数

如果现在我们想要一个函数用来判断它的参数能否被某个整数整除,该怎么办呢?当然,我们可以写出类似 isEvenisMultipleOf3isMultipleOf4 这样的函数,但是这样的函数是无穷无尽的。

另一种显而易见的解决方案是把整数 N 作为一个参数传入。以这种思路,我们可以写出如下的函数:

func isMultiple(multiple: Int, value: Int) -> Bool {
return value % multiple == 0
}

[-4,-2,0,4,7,-8,3].filter(isMultiple)
//错误:filter方法期望传入 Int->Bool ,但是我们给的是 (Int,Int)->Bool
[-4,-2,0,4,7,-8,3].filter(isMultiple(3))
//错误:不能只传入一个参数来调用 `isMultiple`,需要传入两个参数

返回函数的函数

我们现在需要的是一种方法,通过这种方法,只要我们给定一个除数,就可以生成一个 Int -> Bool 类型的函数(这种类型的函数正是 filter 函数所需要的)。那就让我们开始动手构建:一个接收 (multiplier: Int) 的函数,同时它会返回… 另一个 Int -> Bool 类型的函数。

要构建这样的函数有很多种方法,第一种是在 isMultiple 里面 声明一个 Int -> Bool 的函数,然后再将内部声明的函数返回:

func isMultiple(multiplier: Int) -> (Int -> Bool) {
func multFunctionToReturn(value: Int) -> Bool {
return value % multiplier == 0
}
return multFunctionToReturn
}

在这里,内部函数“捕获”了外部函数提供的 multiplier 参数,并生成一个特定的函数,然后将该函数返回。

不过一种更常见(也更紧凑)的方法是使用闭包。在 Swift 当中,函数和闭包是可以互换的,因此,我们可以直接在函数中返回一个 Int -> Bool 类型的闭包:

func isMultiple(multiplier: Int) -> (Int -> Bool) {
return { (value: Int) -> Bool in
value % multiplier == 0
}
}

柯里化

其实,我们还有第三种方法: 柯里化 。 柯里化 可以将一个接收多个参数的函数转化一个只接收一个参数的函数,同时返回另一个函数(返回的函数将接收原函数的下一个参数,再返回一个函数… 直到所有的参数都被消耗掉,并返回一个最终的值)。这种技术可以让我们实现某个函数的部分功能,这在某些情况下是十分强大和有用的。

在 Swift 中,我们可以轻松地将一个接收多个参数的方法转化成柯里化的方法,只要将分隔参数的逗号替换成一个闭括号,紧接着再添加一个开括号。现在,让我们复用第一个 isMultiple 函数的实现,并将它转化成柯里化的函数:

func isMultiple(multiplier: Int)(value: Int) -> Bool {
return value % multiplier == 0
}

这种语法会让人觉得有点困扰,所以就个人来讲,我还是偏向显式地使用返回函数的函数(就像在上一小节中的形式),因此,柯里化的函数可以等价地转化为如下的形式(我觉得这种形式更易懂):

func isMultiple(multiplier: Int) -> (value: Int) -> Bool {
return { value in value % multiplier == 0 }
}

不过使用哪种方式的最终决定权还是在你自己手上。

组合函数

我们下一步要做的事,是将这些筛选函数进行组合,生成一个新的筛选函数。比如说,我们有一个 isPositive 函数和一个 isMultiple 函数,如何筛选出即是正数同时也是偶数的数字呢?

当然我们可以进行两次筛选,就像这样:

[-4,-2,0,4,7,-8,3].filter(isPositive).filter(isMultiple(2))

但是这种方法效率并不高,因为它对数组进行了两次遍历。更进一步地,如果想筛选出数组中的正数 或者 偶数,我们就没有其它更好的语法来表达,只能这样做:

[-4,-2,0,4,7,-8,3].filter { value in isPositive(value) || isMultiple(2)(value) }

如果我们可以组合两个 Int -> Bool 函数并生成一个新的函数,不是更酸爽么?如果像下面这样来写代码,我们应该会觉得更自然:

[-4,-2,0,4,7,-8,3].filter(isPositive || isMultiple(2))

妥妥地,我们来让它实现吧!

&&|| 运算符已经存在了,我们只需要对他们进行重载,好让它们能够接收 Int -> Bool 类型作为参数:

func || (lhs: Int->Bool, rhs: Int->Bool) -> (Int->Bool) {
return { (value: Int) -> Bool in
return lhs(value) || rhs(value)
}
}

我们也可以对 && 运算符进行同样的操作。这次我们将使用 $0 隐式参数,这样可以使代码更紧凑:

func && (lhs: Int->Bool, rhs: Int->Bool) -> (Int->Bool) {
return { lhs($0) && rhs($0) }
}

重写 ! 运行符好让我们可以对一个函数进行取反,如何?

prefix func ! (f: Int->Bool) -> (Int->Bool) {
return { value in !f(value) }
}

现在,我们可以这样来写代码:

[-4,-2,0,4,7,-8,3].filter( !isPositive || isMultiple(3) )
// return [-4,-2,0,-8,3] (non-positive numbers + multiples of 3)

有没有很优雅?

我们甚至可以使用它们来声明负数和奇数/偶数筛选器:

let isZero = { value in value == 0 }
let isPositiveOrZero = isPositive || isZero
let isNegative = !isPositive && !isZero
let isNegativeOrZero = !isPositive
let isEven = isMultiple(2)
let isOdd = !isEven

感谢重载的运算符,以上的代码运行正确无误!

总结

通过这些简单的例子,我们学习到了返回函数的函数的好用之处,同时还学习了如何对运算符进行重载以用来对返回 Bool 类型的函数进行组合,最后还介绍了柯里化的概念。

函数还有很多有乐趣值得我们去挖掘。我们可以使 && , ||! 的重载更加泛型化,以使它们可以接受 T -> Bool 类型的函数,这里的 T 可以是任何类型。我甚至还可以全面地讲解柯里化的知识,解开它的封印,深入探索下函数式编程(不过目前已经有很多其它的博客对这些进行了讲解,因此我觉得不需要再增加我的一篇了),不过我觉得今天这些知识已经足够你充实一整天了!

继续快乐地 Swift 吧!

正文到此结束
Loading...