甩锅申明(原文哪里的我忘记了,等会补上)
本文假设你熟悉swift3
关于函数式编程的介绍
FP(Functional programming)是一种编程范式。与声明式编程不同的是,它强调不可变性(immutable),纯函数(pure function),引用透明(referentially transparent)等等。如果你看到这些术语一脸懵逼,请不要灰心。本文的目的就是介绍这些。swift具有相当多的FP特性,你不应该对这些优秀的性质视而不见。
1.函数是一等公民
什么是一等公民呢(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。(作者原文说所有语言都是这样)
so 我们可以这样
func bar() {
print("foo")
}
let baz = bar
baz() // prints "foo"
也可以这样
func foo() {
print("foo")
}
func bar() {
print("bar")
}
func baz() {
print("baz")
}
let functions = [
foo,
bar,
baz
]
for function in functions {
function() // runs all functions
}
是的,你没看错,函数也能丢到数组里面,你可以做一大堆很cooooool的事情在这种语言里
顺便我们现在来定义一个概念:
Anynonmous(匿名函数) functions a.k.a Closures(闭包)
闭包(closure)是所有函数式语言都具备的一项平常特性,可是相关的论述却常常充斥着晦涩乃至神秘的字眼。所谓闭包,实际上是一种特殊的函数,它在暗地里绑定了函数内部引用的所有变量。换句话说,这种函数(或方法)把它引用的所有东西都放在一个上下文里“包”了起来(原文解释得太简单了)。
闭包在swift有很多种定义的方式(我数了下至少有5,6种吧),最常见的一种方式是
{(parameter1 : Type, parameter2: Type, ..., parameterN: Type) -> ReturnType in
}
他们也可以定义成
let anonymous = { (item: Int) -> Bool in
return item > 2
}
anonymous(3)
哇哇 你不是说函数是匿名的吗,现在怎么有个名字了。
他们确实没有名字,这里只是把一个匿名函数赋值给了一个变量,就像我们之前干的那样,你看下面代码。
({ (item: Int) -> Bool in
return item > 2
}(4)) // returns true(有点像js的立即执行函数)
现在他是一个真正的匿名函数了,我们在定义的时候同时也执行了这个函数。同时值得注意的是内个匿名函数都有他自己的函数类型,函数类型大概看起来是这样的。
let anon : (Int, Bool) -> String = { input,condition in
if condition { return String(input) }
return "Failed"
}
anon(12, true) // returns "12"
anon(12, false) // returns "Failed"
其中的我们定义的类型(可以省略,但是不建议,xcode在解析复杂类型时候会白板):
(Int, Bool) -> String
这表示这个函数接受一个Int和BooL俩个参数并且返回一个String类型,由于函数有类型,编译器在编译的时候会确保类型安全,你可以不写类型,但是类型一定要对(非常不建议不写类型,xcode在解析复杂类型时候会白板):
你可以这么写
var functionArray = [(Int, Bool)->String]()
let one : (Int, Bool) -> String = { a, b in
if b { return "\(a)"}
else { return "Fail" }
}
functionArray.append(one)
let two = { (a: Int, b: Bool) -> String in
if b { return "\(a)" }
else { return "FailTwo" }
}
functionArray.append(two)
上面的代码都可以工作,但是你不能这写:
let three : (Int)->Bool = { item in
return item > 2
}
functionArray.append(three)
你会得到错误信息:
ERROR at line 21, col 22: cannot convert value of type '(Int) -> Bool' to expected argument type '(Int, Bool) -> String'
functionArray.append(three)
大概意思就是类型不对应,你写swift代码时候将会大量面对这这种类型问题,会持续的折磨你。
这种类型语法有时候显得很啰嗦,我们可以用typealias操作符来重新定义一个复杂的类型。
typealias Checker = (Int, Bool) -> String
var funcArray = [Checker]()
let one : Checker = { a, b in
if b { return "\(a)" }
return "Fail"
}
funcArray.append(one)
还有个额外的语法糖,在闭包中可以用$0,$1,$2等来替代函数参数,这种简洁的表达形式,会使我们的代码看上去很干净。现在改写下one函数
let two: Checker = { return $1 ? "\($0)" : "Fail" }
这里面$0,来替代Int,$1替代了Bool
如果你的函数只有一行return也可以不写
2 pure function
纯函数的概念很简单 你可以简单理解为一个数学函数y=f(x)(使对于集合A中的任意一个数x,在集合B中都有唯一确定的数和它对应),也就是说对于一个给定的输入x,函数的输出是唯一的,不依赖于外部状态。同时也不会改变外部的状态。
例如:
func countUp(currentCount: Int) -> Int {
return currentCount + 1
}
这就是个纯函数
var counter = 0
func countUp() -> Int{
counter += 1
return counter
}
但是他不是,他改变了外部的状态。纯函数,变量的不可变性会让你的测试代码变得很容易编写。同时他也很适合并发编程,变量的状态只依赖于他创建的时刻,再也不需要那些幺蛾子的临界区这种东西了。
3. Higher order functions
简而言之,高阶函数就是将其他函数作为参数或者返回类型是一个函数的函数,有了他,你再也不用知道数据是从哪里来了,每一个函数都是为了用小函数组织成更大的函数,函数的参数也是函数,函数返回的也是函数,最后得到一个超级牛逼的函数,最后数据灌进去了。
举个栗子
func filter(sequence: [Int], elementsSmallerThan border: Int) -> [Int] {
var newSequence = [Int]()
for item in sequence {
if item < border {
newSequence.append(item)
}
}
return newSequence
}
filter(sequence: [1,2,3,4], elementsSmallerThan: 3) // returns [1,2]
这个函数筛选比3小的,现在pm改需求了,要比2大。没事在写一个
func filter(sequence: [Int], elementsLargerThan border: Int) -> [Int] {
var newSequence = [Int]()
for item in sequence {
if item > border {
newSequence.append(item)
}
}
return newSequence
}
filter(sequence: [1,2,3,4], elementsLargerThan: 2) // returns [3,4]
要是在改需求呢?难道继续改代码吗
看看用高阶函数是怎么做的
func filter(sequence: [Int], condition: (Int)->Bool) -> [Int] {
var newSequence = [Int]()
for item in sequence {
if condition(item) {
newSequence.append(item)
}
}
return newSequence
}
let sequence = [1,2,3,4]
let smaller = filter(sequence: sequence, condition: { item in
item < 3
})
let larger = filter(sequence: sequence, condition: { item in
item > 2
})
print(smaller) // prints [1,2]
print(larger) // prints [3,4]
现在随便PM怎么改需求,我们只要传递一个函数进去就行了
我们也可以用swift尾闭包的语法糖
// 大概就是闭包是最后一个参数,可以这么写
let equalTo = filter(sequence: sequence) { item in item == 2}
// 一个意思,语法糖而已
let isOne = filter(sequence: sequence) { $0 == 1}
上面写法其实也有很大的局限,因为你只能传入一个函数,不能传递一个带参数的函数,这么说有点抽象,请对着代码理解,下面代码其实使用了柯里化。
typealias Modifier = ([Int]) -> [Int]
func chooseModifier(isHead: Bool) -> Modifier {
if isHead {
return { array in
Array(array.dropLast(1))
}
} else {
return { array in
[array[0]]
}
}
}
let head = chooseModifier(isHead: true)
let tail = chooseModifier(isHead: false)
//这个函数干的事情和函数的命名我有点蒙蔽....
//现在head和tail就是一个函数了,他们的函数签名是([])->[]
//head([1,2,3]) tail([1,2,3])自己试试看效果
我们来定义一个检测一个数时候某个range里的函数
typealias Range = (Int) -> Bool
func range(start: Int, end: Int) -> Range {
return { point in
return (point > start) && (point < end)
}
}
let fourToFifteen = range(start: 4, end: 15)
//Check if a number belongs to a range
print(fourToFifteen(14)) //true
print(fourToFifteen(16)) //false 和上面同理
Conditional parameter evaluation
现在有种情况,你需要根据用的选择来生成一个参数,但是计算的代价非常昂贵。不用担心你现在可以使用高阶函数来使计算推迟到你确定需要使用时,
func stringify1(condition: Bool, parameter: Int) -> String {
if condition {
return "\(parameter)"
}
return "Fail"
}
func stringify2(condition: Bool, parameter: ()->Int) -> String{
if condition {
return "\(parameter())"
}
return "Fail"
}
let a = stringify1(condition: false, parameter: expensiveOperation())
let b = stringify2(condition: false, parameter: { return expensiveOperation()})
第一种是我们常用的形式,你只要调用这个函数就会调用判断函数。
第二种是高阶函数版本,你不走这个判断分支,判断函数是不会走的。
这种凌乱的写法会让人很难掌握,但是swift有个内置的机制来帮我们处理这些乱七八糟的类型,不要你操心
@autoclosure annotation
恩,上面说的就是@autoclosure ,他会自动的把一个表达式转换成一个closure
上代码
func stringify3(condition: Bool, parameter: @autoclosure ()->Int) -> String {
if condition {
return "\(parameter())"
}
return "Fail"
}
现在我们如果想调用stringify3:
stringify3(condition: true, parameter: 12)
上面代码有点不好理解 我在举个栗子
func test(_ p:()->Bool) {
if p() {
}
}
test({return 3>1})
test({ 3>1})
test{ 3>1}
func test1( _ p:@autoclosure ()->Bool) {
if p() {
}
}
test1(3>1)
请自己体会下,这有点抽象。。。
4. Currying
柯里化最大的好处是可以把多参数函数映射到单参数函数,把一个非常复杂的函数分解成一个个简单函数
来看一个最基本的栗子
func add(_ a: Int) -> ((Int)-> Int) {
return { b in
return a + b
}
}
add(2)(3) // returns 5
定义个很普通的add函数,然后调用add(2)(3)这不是什么奇怪的语法糖,而是add(2)本来就是个函数,你可以这么理解
let function = add(2)
function(3)
继续看代码
typealias Modifier = (String)->String
func uppercase() -> Modifier {
return { string in
return string.uppercased()
}
}
func removeLast() -> Modifier {
return { string in
return String(string.characters.dropLast())
}
}
func addSuffix(suffix: String) -> Modifier {
return { string in
return string + suffix
}
}
定义了一大丢乱七八糟的函数 返回值都是(string)-> string
let uppercased = uppercase()("Value")
let removed = removeLast()(uppercased)
let withSuffix = addSuffix("")(removed)
用科里化的写法
let a = addSuffix(suffix: "suffix")(removeLast()(uppercase()("IntitialFunction")))
是不是很神奇?下面的这段有点抽象,有个很炫酷的学术名字compose monad,不要理解这是什么东西,只要知道他是把俩个函数组合在了一起。
func compose(_ left: @escaping Modifier, _ right: @escaping Modifier) -> Modifier {
return { string in
left(right(string))
}
}
现在函数就长这样了
let a = compose(compose(uppercase(), removeLast()), addSuffix(suffix: "Abc"))("IntitialValue")
这东西还是太难懂了,太多的括号看着碍眼,有俩个方法一个是定义个科里化操作符,一种是将Modifier装在一个容器中
struct Modifier {
private let modifier: (String) -> String
init() {
self.modifier = { return $0 }
}
private init(modifier: @escaping (String)->String) {
self.modifier = modifier
}
var uppercase: Modifier {
return Modifier(modifier: { string in
self.modifier(string).uppercased()
})
}
var removeLast : Modifier {
return Modifier(modifier: { string in
return String(self.modifier(string).characters.dropLast())
})
}
func add(suffix: String) -> Modifier {
return Modifier(modifier: { string in
return self.modifier(string) + suffix
})
}
func modify(_ input: String) -> String {
return self.modifier(input)
}
}
// The call is now clean and clearly states which actions happen in
// which order
let modified = Modifier().uppercase.removeLast.add(suffix: "suffix").modify("InputValue")
print(modified)
有了这个Modifier还能这么玩
let modified = Modifier().add(suffix: "Initial").uppercase.add(suffix: "Later").removeLast.removeLast.removeLast.add(suffix: "Other").modify("Value")
The last example which uses struct breaks the functional pattern a little bit since it holds the private modifier variable. It sacrifices some of the safety for a little bit of syntactic sugar. (没理解),下面用科里化操作符来重写一次,他看起来是
ypealias Modifier = (String)->String
func uppercase() -> Modifier {
return { string in
return string.uppercased()
}
}
func removeLast() -> Modifier {
return { string in
return String(string.characters.dropLast())
}
}
func addSuffix(suffix: String) -> Modifier {
return { string in
return string + suffix
}
}
precedencegroup CurryPrecedence {
associativity: left
}
infix operator |> : CurryPrecedence
func |> ( left: @escaping Modifier, right: @escaping Modifier) -> Modifier {
return { string in
right(left(string))
}
}
let modified = uppercase() |> removeLast() |> addSuffix(suffix: "123")
print(modified("Abcd"))