最近初学swift,和OC比,发现语言更现代,也有了更多的特性。如何写好swift代码,也许,熟练使用新特性写出更优秀的代码,就是答案。今天先从大的方向谈谈swift中的编程范式-函数式编程。主要还是读了大佬帖子,写写自己的理解。
"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
它使代码更像自然语言,告诉程序员要干什么,而不是怎么干,把怎么干的细节拆分到各个函数中。调用的地方逻辑清晰,便于debug。
举例来说,现在有这样一个数学表达式:
(1 + 2) * 3 - 4
传统的过程式编程,可能这样写:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
var result = subtract(multiply(add(1,2), 3), 4);
这就是函数式编程。
subtract(multiply(add(1,2), 3), 4)
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。
var print = function(i){ console.log(i);};
[1,2,3].forEach(print);
函数(function)这个名词来自于数学,函数通过一个给定的值,计算出另外一个值,也就是上学时常见的f(x)。
在通常的理解中,下面代码里面,f1,f2,f3通常都被叫做函数:
//伪代码 函数无入参,返回2
def f1(): return 2
//函数有参数x,返回 x+1
def f2(int x): return x+1
//函数无入参,无返回值,打印hello world
def f3(): print("hello world")
复制代码但实际上,函数(function)和procedure是有区别的: function 通过运算返回一个值,而procedure只执行一段代码,没有返回值。 这一点对于后面的理解是非常有帮助的,首先要区分出二者。
再回到上面的代码中,f1,f2,为function而f3为procedure。
Pure:纯的; 单纯的; 纯真的; 干净的 我们将满足下面两个条件的函数称作Pure Function:
def sum(a,b): return a+b
def f(): return 2
print(f() + f())
print(2)
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。
先说两个概念型的名词:
高阶函数(high order func),指可以将其他函数作为参数或者返回结果的函数。
一级函数(first class func),指可以出现在任何其他构件(比如变量)地方的函数。
map { (Element) -> Element in
对 element 进行处理
}
一般用在集合类型,对集合里的元素进行遍历,函数体里实现对每一个元素的操作。
var arr = [1,3,2,4]
let mapres = arr.map {
"NO." + String($0)
}
// 运行结果:["NO.1", "NO.3", "NO.2", "NO.4"]
reduce(Result) { (Result, Element) -> Result in
基于 Result 对当前的 Element 进行操作,并返回新的 Result
}
一般用在集合类型,对集合里的元素进行叠加处理,函数体里传两个参数,第一个是之前的叠加结果,第二个是当前元素,返回值是对当前元素叠加后的结果。
// 对数组里的元素:奇数相加,偶数相乘
var arr = [1,3,2,4]
let reduceRes = arr.reduce((0,1)) { (a:(Int,Int), t:Int) -> (Int,Int) in
if t % 2 == 1 {
return (a.0 + t, a.1)
} else {
return (a.0, a.1 * t)
}
}
// 运行结果:(4,8)
filter { (Element) -> Bool
对元素的筛选条件,返回 Bool
}
一般用在集合类型,对集合里的元素进行筛选。函数体里实现筛选条件,返回 true 的元素通过筛选。
var arr = [1,3,2,4]
let filterRes = arr.filter {
$0 % 2 == 0
}
// 运行结果:[2,4]
首先先看下 Swift 源码里对集合数组的map和flatmap的实现:
// Sequence.swift
extension Sequence {
public func map<T>(_ transform: (Element) -> T) -> [T] {}
}
// SequenceAlgorithms.swift.gyb
extension Sequence {
public func flatMap<T>(_ transform: (Element) -> T?) -> [T] {}
public func flatMap<S : Sequence>(_ transform: (Element) -> S) -> [S.Element] {}
}
前面我们已经知道,map是一种遍历,而上面的代码又显示出来,flatmap有两种重载的函数:
其中一种与map非常相似,差别只在闭包里的返回值变成了可选类型。 另一种稍微有点不同,闭包传入的是数组,最后返回的是数组的元素组成的集合。
// map
let arr = [1,2,nil,4,nil,5]
let arrRes = arr.map { $0 } // 结果为:[Optional(1), Optional(2), nil, Optional(4), nil, Optional(5)]
// flatmap
let brr = [1,2,nil,4,nil,5]
let brrRes = brr.flatmap { $0 } // 结果为:[1, 2, 4, 5]
let crr = [[1,2,4],[5,3,2]]
let ccRes = crr.flatmap { $0 } // 结果为:[1, 2, 4, 5, 3, 2]
let cdRes = crr.flatmap { c in
c.map { $0 * $0 }
} // 结果为[1, 4, 16, 25, 9, 4]
// 使用 map 实现的上面平铺功能
let ceRes = Array(crr.map{ $0 }.joined()) // 同 ccRes
let cfRes = Array(crr.map{ $0 }.joined()).map{ $0 * $0 } // 同 cdRes
简单理解为,flatMap可以将多维数组平铺,也还以过滤掉一维数组中的nil元素。
map和flatMap不只在数组中可以使用,对于 Optional 类型也是可以进行操作的。先看下面这个例子:
let a: Date? = Date()
let formatter = DateFormatter()
formatter.dateStyle = .medium
let c = a.map(formatter.string(from:))
let d = a == nil ? nil : formatter.string(from: a!)
c 和 d 是两种不同的写法,c 写法是不是更优雅一些?