通过Swift学函数式编程

在文章SWIFT IS A LOT LIKE SCALA [1] 提到Swift和Scala有很大的相似之处,在某些特性甚至比Scala对函数式编程的支持更友好。笔者遂从Swift语言出发,学习函数式编程[2] [3],并记录笔记如下。

什么是函式编程

是一种编程范式,强调数学化的函数,不可变(immutable),expressiveness,尽量少地使用变量、状态。

由这些特性带来的好处有:解耦,使App易于测试,易于并发、并行化。

Map Reduce Filter

Array Filter

这些函式方法和Python很像,例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
func isEven(number: Int) -> Bool {
return number % 2 == 0
}

// First-class function, 函数作为变量看待
let evens1 = Array(1...10).filter(isEven)
println(evens1)

// Closure 闭包,匿名函数,即Python里面的Lambda
let evens2 = Array(1...10).filter{
(number) in
number % 2 == 0
}

自制Filter

此处自定义了一个泛型函数Generic Function。

1
2
3
4
5
6
7
8
9
func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
var result = [T]()
for i in source {
if predicate(i) {
result.append(i)
}
}
return result
}

Array Reduce

任务:取1…10的偶数求和

1
2
3
let evenSum = Array(1...10)
.filter { (number) in number % 2 == 0 }
.reduce(0) { (total, number) in total + number }

或者简写

1
2
3
4
5
let evenSum = Array(1...10)
.filter { $0 % 2 == 0 }
.reduce(0) { $0 + $1 }

println(evenSum)

自定义Reduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extension Array {
func myReduce<T,U>(seed:U, combiner:(U,T)->U) -> U
{
var cur = seed
for item in self {
cur = combiner(cur, item as T)
}
return cur
}

let evenSum = Array(1...10)
.filter { (number) in number % 2 == 0 }
.myReduce(0) { (total, number) in total + number }

println(evenSum)

递归Reduce

需先声明Closure类型,再定义行为。参考Advanced Swift at WWDC14 [4] 的视频。

1
2
3
4
5
6
7
8
9
10
11
12
let list: ArraySlice = [1,2,3,4,5,6,7,8,9,10]
var closure:((Int, ArraySlice<Int>) -> Int)!
closure = { (memo, list) in
if (list.count == 0) {
closure = nil // remove retain cycle
return memo
} else {
return closure(memo + list[0], list[1..<list.count])
}
}

let value = closure(0, list)

函数式编程练习:建立字典索引

输入如:

1
2
let words = ["Guinea Pig", "Mouse", "Monkey", 
"Fish", "Dog", "cat", "chicken"]

求输出:

1
2
3
4
5
let dict = [(C, [Cat, Chicken]),
(F, [fish]),
(D, [Dog]),
(M, [Mouse, monkey]),
(G, [Guinea Pig])]

思路1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
extension String {
subscript (i: Int) -> Character {
return self[advance(self.startIndex, i)]
}
func firstCharacter() -> Character {
return self[0] as Character
}
}

typealias WordList = [String]
typealias Entry = (Character, WordList)
typealias Entries = Array<Entry>

func buildIndexOf(words: WordList) -> Entries {
return words
.reduce(Entries()) { (entries, word) in
// 1
// for each word in words
// check if word.firstChar in entries
for entry: Entry in entries as Entries {
let char: Character = entry.0
let word_list: WordList = entry.1
// 2
// Indexed char: add word to word_list
if char == word.uppercaseString.firstCharacter() {
let new_entry = Entry(char, word_list + [word])
// immutable entries, need to create new by append
return entries.filter() {$0.0 != char } + [new_entry]
}
}
// 3
// Unindexed char: create Entry
let entry: Entry = Entry(word.uppercaseString.firstCharacter(), [word])
return entries + [entry]
}
}

let words = ["Guinea Pig", "Mouse", "Monkey",
"Fish", "Dog", "cat", "chicken"]
println(buildIndexOf(words))

思路2:(参见[2] )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func buildIndexOf(words: WordList) -> Entries {
let distinctIndex: [Character] = words.reduce([Character]()) {
(char, word) in
if !contains(char, word.uppercaseString.firstCharacter()) {
return char + [word.uppercaseString.firstCharacter()]
}
return char
}
// distinctIndex = ['A','F','G',...]

// add word into index
let entries: Entries = distinctIndex.map {
char -> Entry in // -> Entry
return Entry(char, words.filter() { char == $0.uppercaseString.firstCharacter() })
}
return entries
}

let words = ["Guinea Pig", "Mouse", "Monkey",
"Fish", "Dog", "cat", "chicken"]

println(buildIndexOf(words))

尾递归 Tail-Recursion


  1. http://leverich.github.io/swiftislikescala/ ↩︎

  2. http://www.raywenderlich.com/82599/swift-functional-programming-tutorial ↩︎

  3. http://jamesonquave.com/blog/functional-programming-in-swift/ ↩︎

  4. https://developer.apple.com/videos/wwdc/2014/#404 ↩︎

本文有帮助?