前言
在写这篇文章之前,我还是没有听过柯里化这个词,这是Swift的新特性,作为一个有追求的工程师,在函数式编程给我们带来的好处背景下,我觉得你想进阶Swift应该有必要掌握的
柯里化函数概念
柯里化(Currying),又称部分求值(Partial Evaluation),是一种函数式编程思想,就是把接受多个参数的函数转换成接收一个单一参数(最初函数的第一个参数)的函数,并且返回一个接受余下参数的新函数技术。
柯里化函数的定义
在swift2.0中是直接支持柯里化式格式的函数的
但是在swift3.0之后就废弃这种写法(函数的 currying 特性的使用场景并不大,但他会增加很多语言的复杂性,所以需要删除它),所以需要我们自己去定义,当然这就是标准的柯里化函数的格式
接下来我就来一步步介绍如何自定义一个柯里化式格式的函数
class Currying {
/*** uncurried:普通函数 ***/
// 接收多个参数的函数
func sum(a: Int, b: Int, c: Int) -> Int {
return a + b + c
}
/*** 手动实现柯里化函数 ***/
// 把上面的函数转换为柯里化函数,首先转成接收第一个参数a,并且返回接收余下第一个参数b的新函数(采用闭包)
// 为了让大家都能看懂,我帮你们拆解来看下
// (a: Int) : 参数
// (b:Int) -> (c: Int) -> Int : 函数返回值(一个接收参数b的函数,并且这个函数又返回一个接收参数c,返回值为Int类型的函数)
// 定义一个接收参数a,并且返回一个接收参数b的函数,并且这个函数又返回一个接收参数c,返回值为Int类型的函数
/*** 手动实现柯里化函数 ***/
/// 把上面的函数转换为柯里化函数,首先转成接收第一个参数a,并且返回接收余下第一个参数b的新函数(采用闭包)
///
/// - Parameter a: 参数
/// - Returns: (_ b:Int) -> (_ c: Int) -> Int : 函数返回值(一个接收参数b的函数,并且这个函数又返回一个接收参数c,返回值为Int类型的函数)
func sumCurrying(a: Int) -> (_ b:Int) -> (_ c:Int) -> Int {
// 这里返回的是(接受b参数返回值为(接受c参数返回值为Int的闭包)的闭包)
return { b in // 闭包的完整形式:b -> ((_ c: Int) -> Int) in
// 这里返回的是(接受c参数返回值为Int的闭包)
return { c in // 闭包的完整形式:c -> Int in
// 这里返回的是最后Int类型的结果
return a + b + c
}
}
}
}
调用柯里化函数
let currying = Currying()
// 柯里化函数调用的最终形式,接下来为了让大家看懂,我会一步步拆分调用
let _ = currying.sumCurrying(a: 10)(20)(30)
// 调用函数,得到一个接受b参数返回值为(接受c参数返回值为Int的闭包)的闭包
let funcB = currying.sumCurrying(a: 10)
// funcB闭包的调用形式,得到一个接受c参数返回值为Int的闭包
let funcC = funcB(20)
// 调用funcC的闭包
let sum = funcC(30)
柯里化函数的好处
1.代码简洁
2.提高代码复用性
3.代码管理方便,相互之间不依赖,每个函数都是一个独立的模块,很容易进行单元测试。
4.易于“并发编程”,因为不修改变量的值,都是返回新值。
在项目中的应用
一、Swift中的一个实例方法只是一个类型方法,它将实例作为参数并返回一个将被应用于实例的函数。
let currying = Currying()
// 柯里化函数调用的最终形式,接下来为了让大家看懂,我会一步步拆分调用
let _ = currying.sumCurrying(a: 10)(20)(30)
// 上面的Curring类中也可以用类方法来调用,这使得类型方法和实例方法之间的关系更加清晰
let sum = Currying.sumCurrying(currying)(a: 10)(20)(30)
二、函数工厂
想象下面向对象编程里的工厂方法。如果有一个工厂返回的是函数,那就正适合柯里化了。
static func adder(a: Int) -> (Int) -> Int {
return { i in
return a + i
}
}
static let incremeter = adder(a: 1)
static let result = incremeter(8)
adder通过柯里化把第一个参数固定为1,返回了一个+1的函数。
三:利用柯里化函数延迟性的特点(柯里化函数代码需要前面的方法调用完成之后,才会来到柯里化函数代码中)去完成特定业务的需求
一个界面的显示,依赖于数据,需要加载完数据之后,才能判断界面显示。这时候也可以利用柯里化函数,来组装界面,把各个模块加载数据的方法抽出来,等全部加载完成,在去执行柯里化函数,柯里化函数主要实现界面的组装。
protocol CombineUI {
func combine(top:@escaping () -> ()) -> (_ bottom: () -> ()) -> ()
}
// 定义一个界面类,遵守组合接口
class UI: CombineUI {
func combine(top: @escaping () -> ()) -> (() -> ()) -> () {
return { bottom in
// 搭建顶部
top()
// 搭建底部
bottom()
}
}
}
四:实现Swift 中 target-action
在 Swift 中 Selector 只能使用字符串在生成。这面临一个很严重的问题,就是难以重构,并且无法在编译期间进行检查,其实这是十分危险的行为。但是 target-action 又是 Cocoa 中如此重要的一种设计模式,无论如何我们都想安全地使用的话,应该怎么办呢?一种可能的解决方式就是利用方法的柯里化。
protocol TargetAction {
func performAction()
}
struct TargetActionWrapper<T: AnyObject>: TargetAction {
weak var target: T?
let action: (T) -> () -> ()
func performAction() {
if let t = target {
action(t)()
}
}
}
enum ControlEvent {
case TouchUpInside
case ValueChanged
// ...
}
class Control {
var actions = [ControlEvent: TargetAction]()
func setTarget<T: AnyObject>(target: T,
action: @escaping (T) -> () -> (),
controlEvent: ControlEvent) {
actions[controlEvent] = TargetActionWrapper(
target: target, action: action)
}
func removeTargetForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent] = nil
}
func performActionForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent]?.performAction()
}
}
// 使用
class ViewController: UIViewController {
let button = Control()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
button.setTarget(target: self, action: ViewController.onButtonTap, controlEvent: .touchUpInside)
}
func onButtonTap() {
print("Button was tapped")
}
}