Swift 相比原先的 Objective-C 最重要的优点之一,就是对函数式编程提供了更好的支持。 Swift 提供了更多的语法糖和一些新特性来增强函数式编程的能力,本文就在这方面进行一些讨论。
Swift 概览
对编程语言有了一些经验的程序员,尤其是那些对多种不同类型的编程语言都有经验的开发者, 在学习新的语言的时候更加得心应手。原因在于编程语言本身也是有各种范式的, 把握住这些特点就可以比较容易的上手了。
在入手一门新的语言的时候,一般关注的内容有:
- 原生数据结构
- 运算符
- 分支控制
- 如果是面向对象的编程语言,其面向对象的实现是怎样的
- 如果是函数式编程语言,其面向函数式编程的实现是怎样的
通过这几个点,其实只要阅读 Swift 文档的第一章,你就可以对这个语言有一个大概的印象。 比如对于数据结构,Swift 和其他的编程语言大体一样,有 Int, Float, Array, Dictionary 等, 运算符也基本与 C 语言一致等。 本文主要集中于对 Swift 函数式编程方面的特点进行一些盘点,因此在这里假设大家对 Swift 的基本语法已经有所了解。
对于一种编程范式,要掌握它也要抓住一些要点。对于支持函数式编程的语言,其一般的特点可能包含以下几种:
- 支持递归
- 函数本身是语言 First Class 的组成要素,且支持高阶函数和闭包
- 函数调用尽可能没有副作用 (Side Effect) 的条件
接下来我们来逐个盘点这些内容。
递归
Swift 是支持递归的,事实上现在不支持递归的编程语言已经很难找到了。在 Swift 里写一个递归调用和其他编程语言并没有什么区别:
1 2 3 4 5 6 7 8 9 |
func fib(n: Int) -> Int { if n <= 1 { return 1 } else { return fib(n-1) + fib(n-2) } } fib(6) // output 13 |
关于 Swift 的递归没有什么好说的。作为一个常识,我们知道递归是需要消耗栈空间的。 在函数式编程语言中,递归是一个非常常用的方法,然而使用不慎很容易导致栈溢出的问题。 如果将代码改写为非递归实现,又可能会导致代码的可读性变差,因此有一个技巧是使用“尾递归”, 然后让编译器来优化代码。
一个 Common Lisp 的尾递归的例子是
1 2 3 4 5 6 7 |
(defun fib(n) (fib-iter 1 0 n)) (defun fib-iter(a b count) (if (= count 0) b (fib-iter (+ a b) a (- count 1)))) |
我们可以把我们上述的 Swift 代码也改写成相同形式
1 2 3 4 5 6 7 8 9 10 11 12 |
func fibiter(a: Int, b: Int, count: Int) -> Int { if count==0 { return b } else { return fibiter(a + b, a, count-1) } } func fib(n: Int) -> Int { return fibiter(1, 1, n); } |
我们可以 Playground 里观察是否使用尾递归时的迭代结果变化。
值得注意的是,这里出现了一个 Swift 的问题。虽然 Swift 支持嵌套函数,但是当我们将fibiter
作为一个高阶函数包含在fib
函数之内的时候却发生了 EXC_BAD_ACCESS 报错, 并不清楚这是语言限制还是 Bug。
Swift 的高阶函数和闭包
在 Objective-C 时代,使用 block 来实现高阶函数或者闭包已经是非常成熟的技术了。 Swift 相比 Objective-C 的提高在于为函数式编程添加了诸多语法上的方便。
首先是高阶函数的支持,可以在函数内定义函数,下面就是一个很简洁的例子。
1 2 3 4 5 6 7 8 9 10 |
func greetingGenerator(object:String) -> (greeting:String) -> String { func sayGreeting(greeting:String) -> String { return greeting + ", " + object } return sayGreeting } let sayToWorld = greetingGenerator("world") sayToWorld(greeting: "Hello") // "Hello, World" sayToWorld(greeting: " 你好 ") // " 你好, World" |
如果使用 block 实现上述功能,可读性就不会有这么好。而且 block 的语法本身也比较怪异, 之前没少被人吐槽。Swift 从这个角度来看比较方便。事实上,在 Swift 里可以将函数当做对象赋值, 这和很多函数式编程语言是一样的。
作为一盘大杂烩,Swift 的函数系统也很有 JavaScript 的影子在里面。比如可以向下面这样定义函数:
1 2 3 4 5 6 |
let add = { (a:Int, b:Int) -> Int in return a+b } add(1, 2) // 3 |
等号之后被赋予变量add
的是一个闭包表达式,因此更准确的说, 这是将一个闭包赋值给常量了。注意在闭包表达式中,in
关键字之前是闭包的形式定义,之后是具体代码实现。 Swift 中的闭包跟匿名函数没有什么区别。 如果你将它赋值给对象,就跟 JavaScript 中相同的实践是一样的了。幸好 Swift 作为 C 系列的语言, 其分支语句 if 等本身是有作用域的,因此不会出现下列 JavaScript 的坑:
在入手一门新的语言的时候,一般关注的内容有:
- 原生数据结构
- 运算符
- 分支控制
- 如果是面向对象的编程语言,其面向对象的实现是怎样的
- 如果是函数式编程语言,其面向函数式编程的实现是怎样的
通过这几个点,其实只要阅读 Swift 文档的第一章,你就可以对这个语言有一个大概的印象。 比如对于数据结构,Swift 和其他的编程语言大体一样,有 Int, Float, Array, Dictionary 等, 运算符也基本与 C 语言一致等。 本文主要集中于对 Swift 函数式编程方面的特点进行一些盘点,因此在这里假设大家对 Swift 的基本语法已经有所了解。
对于一种编程范式,要掌握它也要抓住一些要点。对于支持函数式编程的语言,其一般的特点可能包含以下几种:
- 支持递归
- 函数本身是语言 First Class 的组成要素,且支持高阶函数和闭包
- 函数调用尽可能没有副作用 (Side Effect) 的条件
接下来我们来逐个盘点这些内容。
递归
Swift 是支持递归的,事实上现在不支持递归的编程语言已经很难找到了。在 Swift 里写一个递归调用和其他编程语言并没有什么区别:
1 2 3 4 5 6 7 8 9 |
func fib(n: Int) -> Int { if n <= 1 { return 1 } else { return fib(n-1) + fib(n-2) } } fib(6) // output 13 |
关于 Swift 的递归没有什么好说的。作为一个常识,我们知道递归是需要消耗栈空间的。 在函数式编程语言中,递归是一个非常常用的方法,然而使用不慎很容易导致栈溢出的问题。 如果将代码改写为非递归实现,又可能会导致代码的可读性变差,因此有一个技巧是使用“尾递归”, 然后让编译器来优化代码。
一个 Common Lisp 的尾递归的例子是
1 2 3 4 5 6 7 |
(defun fib(n) (fib-iter 1 0 n)) (defun fib-iter(a b count) (if (= count 0) b (fib-iter (+ a b) a (- count 1)))) |
我们可以把我们上述的 Swift 代码也改写成相同形式
1 2 3 4 5 6 7 8 9 10 11 12 |
func fibiter(a: Int, b: Int, count: Int) -> Int { if count==0 { return b } else { return fibiter(a + b, a, count-1) } } func fib(n: Int) -> Int { return fibiter(1, 1, n); } |
我们可以 Playground 里观察是否使用尾递归时的迭代结果变化。
值得注意的是,这里出现了一个 Swift 的问题。虽然 Swift 支持嵌套函数,但是当我们将fibiter
作为一个高阶函数包含在fib
函数之内的时候却发生了 EXC_BAD_ACCESS 报错, 并不清楚这是语言限制还是 Bug。
Swift 的高阶函数和闭包
在 Objective-C 时代,使用 block 来实现高阶函数或者闭包已经是非常成熟的技术了。 Swift 相比 Objective-C 的提高在于为函数式编程添加了诸多语法上的方便。
首先是高阶函数的支持,可以在函数内定义函数,下面就是一个很简洁的例子。
1 2 3 4 5 6 7 8 9 10 |
func greetingGenerator(object:String) -> (greeting:String) -> String { func sayGreeting(greeting:String) -> String { return greeting + ", " + object } return sayGreeting } let sayToWorld = greetingGenerator("world") sayToWorld(greeting: "Hello") // "Hello, World" sayToWorld(greeting: " 你好 ") // " 你好, World" |
如果使用 block 实现上述功能,可读性就不会有这么好。而且 block 的语法本身也比较怪异, 之前没少被人吐槽。Swift 从这个角度来看比较方便。事实上,在 Swift 里可以将函数当做对象赋值, 这和很多函数式编程语言是一样的。
作为一盘大杂烩,Swift 的函数系统也很有 JavaScript 的影子在里面。比如可以向下面这样定义函数:
1 2 3 4 5 6 |
let add = { (a:Int, b:Int) -> Int in return a+b } add(1, 2) // 3 |
等号之后被赋予变量add
的是一个闭包表达式,因此更准确的说, 这是将一个闭包赋值给常量了。注意在闭包表达式中,in
关键字之前是闭包的形式定义,之后是具体代码实现。 Swift 中的闭包跟匿名函数没有什么区别。 如果你将它赋值给对象,就跟 JavaScript 中相同的实践是一样的了。幸好 Swift 作为 C 系列的语言, 其分支语句 if 等本身是有作用域的,因此不会出现下列 JavaScript 的坑: