函数的柯里化
Swift 里可以将方法进行柯里化 (Currying),这是也就是把接受多个参数的方法进行一些变形,使其更加灵活的方法。
在非函数式的代码时,我们可能会这样写一个方法:
1
2
3
func isNumber(_ base: Int, greaterThan target: Int) -> Bool {
return base > target
}
同样的功能如果使用柯里化的函数:
1
2
3
4
5
6
7
8
func greaterThan(comparer: Int) -> Int -> Bool {
return { $0 > comparer }
}
let greaterThan10 = greaterThan(10);
greaterThan10(13) // => true
greaterThan10(9) // => false
柯里化是一种量产相似方法的好办法。
使用函数当做对象
我们可以结合函数实现诸多简便的功能,在 Swift 中,函数也是一等公民
,利用函数本身的特点,可以更高效的组合出想要的功能。
举例:我们可以给Array<Element>
添加方法,获取一个contains
的(Element) -> Bool
的类型的函数输出。
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
extension Array where Element: Equatable {
public static func contains<T: Equatable>(in array: [T]) -> (T) -> Bool {
return { obj in
return (array.filter { $0 == obj }).count > 0
}
}
public static func notContains<T: Equatable>(in array: [T]) -> (T) -> Bool {
return { obj in
return !contains(in: array)(obj)
}
}
public func intersection(with other: [Element]) -> [Element] {
return self.filter(Array.contains(in: other))
}
public func union(with other: [Element]) -> [Element] {
return self + other.minus(with: self)
}
public func minus(with other: [Element]) -> [Element] {
return self.filter(Array.notContains(in: other))
}
}
let contains = Array.contains(in: [1, 2, 3])
contains(1) ==> true
contains(4) ==> false
[1, 2, 3].intersection(with: [2, 3]) ==> [2, 3]
[1, 2, 3].minus(with: [2, 3]) ==> [1]
[1, 2, 3].union(with: [3, 4]) ==> [1, 2, 3, 4]
对于任何实现了Equatable
的对象集合,都可以使用这些方法做运算。
Tuple 元组
Tuple 是 Swift 带给我们的新功能,在普通场景下使用的时候,主要有两点功能:
- 标明含义:自己或其他小伙伴在接收到这个参数时,很容易根据参数标识来分辨元组内的属性含义。
1
2
3
4
5
switch some {
case let x:
x.up = ...
x.down = ...
}
- 函数的多结果输出:当一个函数想输出多个数据,而数据之间毫无关联,不能结合成一个对象;或信息太少不至于用对象代替时,我们可以使用多元组当做函数的输出。这点跟Go语言的函数多返回值思想很像。
1
func requestCargoInfo(from url: URL) -> (name: String, destination: String) { ... }
@autoclosure
@autoclosure 可以说是 Apple 的一个非常神奇的创造,因为这更多地是像在 “hack” 这门语言。简单说,@autoclosure 做的事情就是把一句表达式自动地封装成一个闭包 (closure)。这样有时候在语法上看起来就会非常漂亮。
比如系统的 precondition 函数,如果我们自己写一个相似功能的:
1
2
3
func precondition(_ condition: () -> Bool, _ message: String ...) {...}
precondition({ return 2 > 1 }, "true")
但是使用@autoclosure
之后,如系统的:
1
2
3
4
5
public func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default,
file: StaticString = #file, line: UInt = #line)
// 我们就可以更简洁的使用:
precondition(2 > 1, "true")
另一种使用方式,在 Vapor 源码中,为了避免过度使用资源、耗费性能,通常是确定某个条件之后再使用某个属性。比如这段代码:
1
2
3
4
5
6
7
8
public func unwrap(or error: @autoclosure @escaping () -> Error) -> Future<Expectation.WrappedType> {
return map(to: Expectation.WrappedType.self) { optional in
guard let wrapped = optional.wrapped else {
throw error()
}
return wrapped
}
}
这里 Error 是在 optional 确定不为 nil 之后再生成的。最后要提一句的是,@autoclosure 并不支持带有输入参数的写法,也就是说只有形如 () -> T 的参数才能使用这个特性进行简化。