在先了解什么是函数式编程之前我们先来看一下 Array 和 Optional 中一些常见的函数。
一、Array的常见函数
1、map
Array 中 map 函数的定义:
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
map 函数要求传一个参数,这个参数可以传一个函数或者闭包表达式,这个闭包表达式的参数到时候给的是数组中的元素并且返回一个值,最后 map 函数的返回值是一个数组。并且,闭包表达式的返回值是一个泛型,也就意味着 map 函数返回的数组中的元素也是一个泛型,和闭包表达式的返回值一一对应。
下面是调用 map 函数的例子:
let arr = [1, 2, 3, 4]
let arr2 = arr.map { element in
return element * 2
}
print(arr2) // [2, 4, 6, 8]
map 函数会将数组中的所有元素进行遍历,然后将元素传给闭包表达式。在闭包表达式中将数组的元素乘以 2 作为返回值,最终 map 函数返回新的数组的元素就是闭包表达式中的返回值。也就是 map 函数会将数组的元素进行映射。
可以将上面的例子进行简写,如下代码:
let arr2 = arr.map { $0 * 2 }
2、filter
Array 中 filter 函数的定义:
@inlinable public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]
filter 函数也是返回一个数组,参数也是要求传一个函数或者闭包表达式,但是这个闭包表达式的返回值是一个 Bool 类型。filter 函数可以对数组进行过滤,比如下面这个例子,取出 arr 中的偶数值。
let arr = [1, 2, 3, 4]
let arr2 = arr.filter { element in
return element % 2 == 0
}
print(arr2) // [2, 4]
filter 函数会将数组中的所有元素进行遍历,然后将元素传给闭包表达式,接下来可以通过闭包表达式的返回值来决定要不要将这个元素进行过滤。
3、reduce
Array 中 reduce 函数的定义:
@inlinable public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
reduce 函数要求传两个参数,第一个参数是一个泛型,第二个参数是一个函数或者闭包表达式。reduce 的返回值为泛型,并且和 initialResult 的类型一样。
下面是一个使用 reduce 函数的例子:
let arr = [1, 2, 3, 4]
let result = arr.reduce(0) { partialResult, element in
return partialResult + element
}
print(result) // 10
reduce 函数会将数组所有的元素进行遍历。参数 initialResult 用作初始累加值的值,比如一开始传的是 0,那么在第一次执行 nextPartialResult 函数的时候,参数 partialResult 为 0。而 nextPartialResult 函数在从第二次执行开始,partialResult 是上一次执行 nextPartialResult 函数的结果,element 是数组的元素。
上面的例子中 reduce 函数返回的结果其实就是数组中所有元素相加的结果。可以将上面的例子进行简写,如下代码:
let arr2 = arr.reduce(0) { $0 + $1 }
或者:
let arr2 = arr.reduce(0, +)
如果需要用到上一次遍历的结果就可以使用 reduce 函数。
4、flatMap
Array 中 flatMap 函数的定义:
@inlinable public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
flatMap 函数要求传一个函数或者闭包表达式,返回一个数组。闭包表达式的参数是一个数组的元素,闭包表达式的返回值要求遵守 Sequence 协议。并且 flatMap 函数的返回数组的元素是闭包表达式返回的数组中的元素。
先来看一个 map 函数的例子:
let arr = [1, 2, 3, 4]
let arr2 = arr.map { element in
Array(repeating: element, count: element)
}
print(arr2) // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
下面是一个使用 flatMap 函数的例子:
let arr2 = arr.flatMap { element in
Array(repeating: element, count: element)
}
print(arr2) // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
通过上面的例子你会发现,flatMap 函数与 map 函数的区别在于 flatMap 函数会将 SegmentOfResult 中的元素 “压平”,然后返回一个包含 SegmentOfResult.Element 的 Sequence 类型。而 map 函数返回的数组中的类型是闭包表达式返回值的类型。
5、compactMap
Array 中 compactMap 函数的定义:
@inlinable public func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
compactMap 函数要求传一个函数或者闭包表达式,返回一个数组。注意看,闭包表达式的返回值为一个可选类型。
我们来看一个的例子:
let possibleNumbers = ["1", "2", "three", "///4///", "5"]
let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
print(mapped) // [Optional(1), Optional(2), nil, nil, Optional(5)]
let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
print(compactMapped) // [1, 2, 5]
通过打印你会发现,Int(str) 返回的是一个 Int? 类型的值,如果无法转成 Int 类型,则 mapped 存储的值就是 nil,比如 "three" 字符串无法转成 Int 类型,所以为 nil,而 mapped 中存储相应的值也是 nil。否则存储的就是可选类型。
而 compactMap 函数会把 Int? 类型进行解包,并且,如果返回的类型为 nil 的话,nil 值不会存储到数组中。compactMap 函数会将 nil 过滤。
6、使用 reduce 函数实现 map、filter 的功能
var arr = [1, 2, 3, 4]
这是一个数组,我们通过 map 函数映射出数组中元素的倍数,代码如下:
// [2, 4, 6, 8]
var arr1 = arr.map { $0 * 2}
我们也可以通过 reduce 函数来实现 map 函数的功能,代码如下:
// [2, 4, 6, 8]
var arr2 = arr.reduce([]) { $0 + [$1 * 2] }
首先传一个空的数组,第一次执行的时候 1 为 1,所以 $1 * 2 = 2,当这个函数遍历完成的时候,最终实现的效果和 map 函数是一样的。
我们也可以通过 reduce 函数来实现 filter 函数的功能,下面是一个使用 filter 函数过滤非偶数的例子:
// [2, 4]
var arr1 = arr.filter { $0 % 2 == 0 }
使用 reduce 函数过滤非偶数的例子:
// [2, 4]
var arr2 = arr.reduce([]){ $1 % 2 == 0 ? $0 + [$1] : $0 }
7、lazy 的优化
下面是一个使用 map 函数的例子:
var arr = [1, 2, 3, 4]
var arr1 = arr.map { element -> Int in
print("mapping \(element)")
return element * 2
}
打印结果:
mapping1
mapping2
mapping3
mapping4
通过打印结果得知,map 函数在使用的时候一定会从头遍历到结束,有时候在使用 map 函数的时候,尾随闭包中的操作可能会比较复杂,如果每次都执行会非常的消耗性能。但我们不希望每次都执行,而是在需要的时候才去执行,此时可以通过 lazy 来保证在使用的时候才会去执行。
代码如下:
var arr = [1, 2, 3, 4]
var arr1 = arr.lazy.map { element -> Int in
print("mapping \(element)")
return element * 2
}
// mapping 2
arr1[1]
// mapping 4
arr1[3]
通过打印结果得知 map 前面加上 lazy 之后,并不会立刻执行尾随闭包中的代码,当需要对 arr1 取值的时候,才会去执行,并且取一次对应的也只执行一次。
二、Optional 的 map 和 flatMap
Optional 类型也有 map 函数和 flatMap 函数,那它们能用来干什么呢?我们先来看一个例子:
var num1: Int? = 10
var num2 = num1.map { $0 * 2 }
// Optional(20)
print(num2)
var num3: Int? = nil
var num4 = num3.map { $0 * 2 }
// nil
print(num4)
调用 map 函数,当这个 Optional 的实例不为 nil 的时候,map 函数会在尾随闭包中将实例(num1)进行解包,然后将尾随闭包的结果返回,返回的同时,map 函数会将结果包装成 Optional 类型的实例,所以打印的结果为:Optional(20);如果 Optional 的实例为 nil,则不会执行 map 函数的尾随闭包,所以 num4 的值打印出来的是 nil。
那么 Optional 的 flatMap 函数和 map 函数又有什么区别呢?我们来看一个例子:
var num1: Int? = 10
var num2 = num1.map { Optional.some($0 * 2) }
var num3 = num1.flatMap { Optional.some($0 * 2) }
// Optional(Optional(20))
print(num2)
// Optional(20)
print(num3)
当我们在 map 函数的尾随闭包中包装一层 Optional 再返回给 num2 的时候,会发现 num2 有两个 Optional 进行包装。而 flatMap 函数并没有,因为它知道返回的结果本身就是一个 Optional,没有必要再一次包装一个 Optional。
了解 Optional 的 map 函数和 flatMap 函数之后,它们能用来干什么呢,先来看第一个使用场景:
var num1: Int? = 10
在平时的开发中,拿到的有可能是一个 Int? 类型的值,当 Int? 的值不为 nil 的时候,需要用它和一个数进行相加,我们可能理所当然的写出这样的代码:
let num2 = 0
if let num1 = num1 {
num2 = num1 + 10
}
或者:
var num2 = (num1 != nil) ? (num1! + 10) : nil
我们发现,这么写虽然也能实现,但不是那么的舒服,好看,比较麻烦。而且既然都是需要对 Int? 类型的 num1 进行解包再拿到解包后的值进行相加,是不是也可以使用 map 函数来实现呢?
代码如下:
var num2 = num1.map { $0 + 10 }
这样子看是不是舒服多了呢,不需要像可选绑定一样需要先声明一个 num2,也不需要像三目运算一样判 nil 然后进行强制解包。
接下来看第二个使用场景,在平时的开发中,难免会使用到 DateFormatter,例如:
var fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
var dateStr: String? = "2011-09-10"
在平时的开发中,我们拿到的日期字符串有可能是为 nil 的,所以 dateStr 是一个 String? 类型的,但是通过 DateFormatter 将日期字符串转成 Date 的时候,传的字符串必须是有值的,那我们可能理所当然的写出这样的代码:
var date = str != nil ? fmt.date(from:str!) : nil
此时会发现通过三目运算来写的时候不是那么的舒服,好看。对于这个问题我们可以通过 flatMap 函数来实现,代码如下:
var date = str.flatMap { fmt.date(from: $0) }
还有更精简的写法:
var date = str.flatMap(fmt.date)
当使用 fmt.date(from:) 将日期字符串转成 Date 的时候,很可能为 nil,那么此时使用 map 函数的话会多包装一层 Optional,所以这里使用 flatMap 函数来实现。
接下来看第三个使用场景,这个使用场景应该经常会遇到,字符串的拼接,代码如下:
var score: Int? = 98
var str = score != nil ? "socreis\(score!)" : "No score"
我们可能需要对某一个字段拼接成字符串,用于显示,但这个字段很可能为 nil,所以需要对其解包,然后才能拼接。此时我们也可以使用 map 函数来实现,代码如下:
var str = score.map { "score is \($0)" } ?? "No score"
这个时候就可以保证 str 是一个 String 类型的。
接下来看第四个使用场景,这个场景也应该经常会遇到,就是数据的处理。在使用 UITableView 的时候,需要响应 cell 中的某个事件,从而对数组中的 Model 进行修改,然后刷新数据。并且我们刷新数据的时候不希望刷新整个表格,而是刷新修改对应的 row,此时就需要拿到修改的 Model 在数组中的位置,也就是拿到索引,然后再通过 IndexPath 进行刷新。
根据这个场景,我们可以配合 Array 的 firstIndex 函数将索引取出,代码如下:
let items = [11, 22, 33, 44, 55]
let index = items.firstIndex(of: 33)
// Optional(2)
print(index)
此时拿到索引就可以更新对应的 row 了。
还有一点,例如我们需要通过这个索引拿到数组的元素,但是拿到的索引是一个可选值,怎么办呢,我们也可以配合 map 函数来拿到数组的元素,例如:
let element = index.map { items[$0] }
// Optional(33)
print(element)
三、 函数式编程
函数式编程(FuntionalProgramming,简称FP)是一种编程范式,也就是如何编写程序的方法论。
主要思想:把计算过程尽量分解成一系列可复用函数的调用。
主要特征:函数是“第一等公民”,与其他数据类型一样的地位,可以赋值给其他变量,也可以作为函数参数、函数返回值。
函数式编程最早出现在 LISP 语言,绝大部分的现代编程语言也对函数式编程做了不同程度的支持,比如 Haskell、JavaScript、Python、Swift、Kotlin、Scala 等。
函数式编程中几个常用的概念 Higher-OrderFunction、FunctionCurrying Functor、ApplicativeFunctor、Monad。
这是关于函数式编程的参考资料:
adit.io/posts/2013-… mokacoding.com/blog/functo…
1、传统写法和函数式写法
假设要实现这么一个功能:[(num + 3) * 5 - 1] % 10 / 2,num 等于 1。
传统的写法:
let num = 1
let num = 1
func add(_ v1: Int,_ v2: Int) -> Int { v1 + v2 } // 加
func sub(_ v1: Int,_ v2: Int) -> Int { v1 - v2 } // 减
func multiple(_ v1: Int,_ v2: Int) -> Int{ v1 * v2 } // 乘
func divide(_ v1: Int,_ v2: Int) -> Int{ v1 / v2 } // 除
func mod(_ v1: Int,_ v2: Int) -> Int { v1 % v2 } // 余
let result = divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
print(result) // 4
此时你会发现,这么去写看着比较晕,感觉有点恶心,这还是在参数比较少的情况下,如果参数比较多的话更恶心了。
那如果用函数式编程的思想要怎么去实现呢?比如 add(v1:v2:) 方法,传统的写法是接收两个参数,然后进行相加,那函数式的写法应该是接收一个参数,返回一个函数,也就是:add(3)(num) 这种写法,我们来看下它的实现:
func add(_ v: Int) -> (Int) -> Int {
return {
return $0 + v
}
}
精简一点的写法应该是这样的:
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
相对应的,其它函数的函数式写法也应该这么写:
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
func sub(_ v: Int) -> (Int) -> Int { { $0 - v } }
func multiple(_ v: Int) -> (Int) -> Int { { $0 * v } }
func divide(_ v: Int) -> (Int) -> Int { { $0 / v } }
func mod(_ v: Int) -> (Int) -> Int { { $0 % v } }
let result = divide(2)(mod(10)(sub(1)(multiple(5)(add(3)(num)))))
print(result) // 4
修改成函数式的写法之后貌似在调用的时候没什么变化,还是一样的难看懂。别着急,我们可以先把“入口”定义好,代码如下:
let fn1 = add(3)
let fn2 = multiple(5)
let fn3 = sub(1)
let fn4 = mod(10)
let fn5 = divide(2)
let result = fn5(fn4(fn3(fn2(fn1(num)))))
print(result) // 4
此时你会发现,我们在调用的时候只需要传一个 num 值就好了,什么加 3,乘 5,减 1 的都不用管了,是不是比刚才好了很多呢。其实我们还可以再优化这个写法,怎么优化呢,先来看一下一个东西:函数合成。
func composite(fn1:@escaping (Int) -> (Int),
fn2:@escaping (Int) -> (Int))
-> (Int) -> Int {
return {
fn2(fn1($0))
}
}
let fn = composite(fn1: add(3), fn2: multiple(5))
通过 composite 函数将 add 函数和 multiple 函数合成了之后,我们在调用 fn 的时候,是不是就可以实现 (num + 3) * 5 的功能了呢。那么 fn 这个函数是既然是先经历 add(3) 再经历 multiple(5),我们是不是可以把这种传参的写法改成 add(3) >>> multiple(5) 这种呢,代码如下:
infix operator >>>: AdditionPrecedence
func >>>(fn1:@escaping (Int) -> (Int),
fn2:@escaping (Int) -> (Int))
-> (Int) -> Int {
return {
fn2(fn1($0))
}
}
let fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
let result = fn(num)
print(result) // 4
结果依然是 4,也就意味到现在,我们从最开始的调用方式:
let result = divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)
变成了这种方式:
let fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
let result = fn(num)
这种方式是不是看起来就好看很多,不像传统的写法那么恶心了呢,最后我们也可以把这个函数的合成再优化一下,通过泛型来实现,代码如下:
infix operator >>>: AdditionPrecedence
func >>><A, B, C>(fn1:@escaping (A) -> (B),
fn2:@escaping (B) -> (C))
-> (A) -> C {
return {
fn2(fn1($0))
}
}
注意看,泛型 A,B,C 作为参数和返回值是有要求的,fn1 的参数为泛型 A 的时候,>>> 函数返回的函数的参数也必须是泛型 A,因为 $0 最终要传给 fn1 的参数。而 >>> 函数的函数的返回值必须和 fn2 的返回值一样,因为在函数返回的是 fn2 的返回值。
2、高阶函数(Higher-OrderFunction)
高阶函数其实也是一个函数,它至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入(map、filter、reduce 等)。
- 返回一个函数。
像上面 Array 的 map、filter、reduce、flatMap 等函数都是高阶函数。在函数式编程中到处都是高阶函数,又例如上面的例子中 add 函数:
func add(_ v: Int) -> (Int) -> Int { { $0 + v } }
这个也可以称之为高阶函数。
3、柯里化(Currying)
柯里化指的是将一个接受多参数的函数变换为一系列只接受单个参数的函数。例如上面 add 方法的转变:
Array、Optional 的 map 方法接收的参数就是一个柯里化函数。我们可以将下面的两个函数进行柯里化。
func add1(_ v1: Int,_ v2: Int) -> Int { v1 + v2 }
func add2(_ v1: Int,_ v2: Int,_ v3: Int) -> Int { v1 + v2 + v3 }
let num1 = add1(10, 20)
let num2 = add2(10, 20, 30)
柯里化后的函数:
func add3(_ v: Int) -> (Int) -> Int { { $0 + v } }
func add4(_ v: Int) -> (Int) -> (Int) -> Int { { a in { b in v + b + a } } }
let num1 = add1(10)(20)
let num2 = add2(10)(20)(30)
此时你会发现,add1 函数和 add2 函数的 Currying 版本是我们手敲实现的,那如果以后再有类似的函数,但是功能不同,我们还需要再次手敲,比较麻烦。那么我们就可以实现一个自动将函数 Currying 的方法,代码如下:
func currying<A, B, C>(fn:@escaping (_ v1: A,_ v2: B) -> C) -> (B) -> (A) -> C {
return { b in
return { a in
return fn(a, b)
}
}
}
func currying<A, B, C, D>(_ fn:@escaping (_ v1: A,_ v2: B, _ v3: C) -> D) -> (C) -> (B) -> (A) -> D {
return { c in
return { b in
return { a in
return fn(a, b, c)
}
}
}
}
其实这个 currying 函数实现的思路就是接收一个传统的函数,返回一个 Currying 版本的函数,只要把传入的函数声明的泛型 A,B,C 和返回的 Currying 版本的函数的 A,B,C 对应起来就 OK 了。调用 currying 的代码如下:
let add1_currying = currying(add1)
let add2_currying = currying(add2)
let num1 = add1_currying(10)(20)
let num2 = add2_currying(10)(20)(30)
此时我们回想到上面假设要实现的功能:[(num + 3) * 5 - 1] % 10 / 2,num 等于 1。在开始的时候我们是手动的把加减乘除这些函数手动的实现它们的 Currying 版本,接下来我们将用刚刚的 currying 函数自动的将这些函数转成 Currying 版本。
需要注意的是不希望使用 currying,而是使用运算符,代码如下:
prefix func ~<A, B, C>(_ fn:@escaping (_ v1: A,_ v2: B) -> C)
-> (B) -> (A) -> C {
{ b in { a in fn(a, b) } }
}
infix operator >>>: AdditionPrecedence
func >>><A, B, C>(fn1:@escaping (A) -> (B),
fn2:@escaping (B) -> (C))
-> (A) -> C {
{ fn2(fn1($0)) }
}
func add(_ v1: Int,_ v2: Int) -> Int { v1 + v2 } // 加
func sub(_ v1: Int,_ v2: Int) -> Int { v1 - v2 } // 减
func multiple(_ v1: Int,_ v2: Int) -> Int{ v1 * v2 } // 乘
func divide(_ v1: Int,_ v2: Int) -> Int{ v1 / v2 } // 除
func mod(_ v1: Int,_ v2: Int) -> Int { v1 % v2 } // 余
let num = 1
let fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
let result = fn(num)
print(result) // 4
只要在方法名前加上 ~ 运算符就可以将传统的函数自动转成 Currying 版本的函数。
4、函子(Functor)
像 Array、Optional 这样支持 map 运算的类型,称为函子(Functor)。我们来看一下 Array、Optional 的 map 函数的定义。
// Array
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
// Optional
@inlinable public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
看了 Array、Optional 的 map 函数的定义,这里我们思考一个问题,什么样的类型才能称之为函子呢?来看一个例子:
func map<T>(_ fn:(Inner) -> T) -> Type<T>
注意看这个 map 函数,首先它要求传一个函数,这个函数的参数是这个类型存储的数据,并返回值是泛型 T;其次 map 函数的返回值的类型是自己本身(Type),并且支持泛型(Type<T>)。
当 Type 的 map 函数满足这个要求的,这个 Type 就可以称之为函子,所以 Array、Optional 这样支持 map 运算的类型,可以称为函子。我们看一张图:
这张图的意思是,现在需要用 3 加上 盒子里面的 2,那就需要取出盒子里的 2 然后加 3,把得出来的结果 5 用一个盒子进行包装。这个不就和 Swift 的 Optional 是一样的吗。
如果这个盒子里什么都没有的话,是不能加 3 的,如图:
我们再来看另一张图:
这张图有个小火车,火车的每节车厢都装着东西,接下来就是取出每个车厢的东西进行一些操作,然后把结果再放回车厢。这个和 Swift 的 Array 是一样的。
5、适用函子(ApplicativeFunctor)
对任意一个函子 F,如果能支持以下运算,该函子就是一个适用函子。
func pure<A>(_value: A)-> F<A>
func <*><A,B>(fn: F<(A) ->B >, value: F<A>) -> F<B>
比如函子 Optional,如果可以支持上面两个函数的运算,那么 Optional 就是一个适用函子。很显然,Optional 是支持的,所以 Optional 是一个适用函子,它的实现如下:
func pure<A>(_ value: A) -> A? { value }
infix operator <*> : AdditionPrecedence
func <*><A,B>(fn: ((A) -> B)?, value: A?) -> B? {
guard let f = fn, let v = value else {
return nil
}
return f(v)
}
var value: Int? = 10
var fn: ((Int) -> Int)? = { $0 * 2 }
// Optional(20)
print(fn <*> value as Any)
除了 Optional 之外,Array 也可以成为适用函子,我们来看一下:
func pure<A>(_ value: A) -> [A] { [value] }
infix operator <*> : AdditionPrecedence
func <*><A,B>(fn: [(A) -> B], value: [A]) -> [B] {
var arr: [B] = []
if fn.count == value.count {
for i in fn.startIndex..<fn.endIndex {
arr.append(fn[i](value[i]))
}
}
return arr
}
// [10]
print(pure(10))
var arr = [{ $0 * 2 }, { $0 + 10 }, { $0 - 5 }] <*> [1, 2, 3]
// [2,12,-2]
print(arr)
6、单子(Monad)
对任意一个类型F,如果能支持以下运算,那么就可以称为是一个单子(Monad)。=
func pure<A>(_ value: A) -> F<A>
func flatMap<A,B>(_ value: F<A>,_ fn: (A) -> F<B>) -> F<B>
很显然,Array、Optional 都是单子。
结尾: 其实这篇文章的目的是为了让读者了解 Swift 中 Array 和 Optional 中一些高阶函数的用法和适用场景,然后再去了解什么是函数式编程,对于函数式编程,这里仅仅只是作为一个了解,因为本人对函数式编程的研究也没有那么深,都是看各路大神的文章。
当然,如果想去深入了解函数式编程的话可以去文章中提到的两个参考资料: