柯里化初探

作者: xqqlv | 来源:发表于2018-03-11 17:56 被阅读4次

    前言

    在写这篇文章之前,我还是没有听过柯里化这个词,这是Swift的新特性,作为一个有追求的工程师,在函数式编程给我们带来的好处背景下,我觉得你想进阶Swift应该有必要掌握的

    柯里化函数概念

    柯里化(Currying),又称部分求值(Partial Evaluation),是一种函数式编程思想,就是把接受多个参数的函数转换成接收一个单一参数(最初函数的第一个参数)的函数,并且返回一个接受余下参数的新函数技术。

    柯里化函数的定义

    在swift2.0中是直接支持柯里化式格式的函数的


    304825-e104cf219377f9ad.png

    但是在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")
        }
    }
    

    要强调的一个思维的转化是,函数式编程思想,思考的单位不再是对象,而是函数。使用上的区别是,在某个方法里,如果需要这样一个函数,只需要一个声明,外面传递进来。至于这个函数在哪个对象上实现的不需要关心。和面向接口编程更配哦。

    相关文章

      网友评论

        本文标题:柯里化初探

        本文链接:https://www.haomeiwen.com/subject/byxwfftx.html