Swift Currying

作者: 流水_事 | 来源:发表于2016-04-15 12:39 被阅读304次

    本博客主要是对下面博客的翻译:
    https://robots.thoughtbot.com/introduction-to-function-currying-in-swift

    什么是函数柯里化:
    在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

    概念很抽象,让我们从一个简单的例子开始逐步了解到底什么是函数柯里化。
    我们有一个接受两个Int类型的参数的Add函数

    func add(a: Int, b: Int) -> Int {
      return a + b
    }
    

    当我们调用这个函数时:

    let sum = add(2, 3) // sum = 5
    

    现在有一个需求,我们要将一个整数集合里面的每一个元素都加2.
    那么我们可以这样写

    let xs = 1...100
    let x = xs.map { add($0, 2) } // x = [3, 4, 5, 6, etc]
    

    这样写看起来还行。但是我总觉得很奇怪。因为我要为Add这个函数创建一个闭包。
    如果我们只关注函数的类型,就会发现Add实际上是一个(Int,Int)->Int的函数。但是在这里,我们想要传给map函数的,实际上是一个(A)->B的函数。

    所以,如果我们有一个(A)->B这样的函数,那么我们就不必传入一个闭包,而是直接传这个函数就好了。所以我们可以这样:

    func addTwo(a: Int) -> Int {
      return add(a, 2)
    }
    
    let xs = 1...100
    let x = xs.map(addTwo) // x = [3, 4, 5, 6, etc]
    

    但是这个函数其实把“2”写死了。那如果我还要+3,+4...+100呢?
    所以我们最好写一个比较通用的函数。

    func add(a: Int) -> (Int -> Int) {
      return { b in a + b }
    }
    

    那么我们现在可以这样子用了:

    let sum = add(2)(3) // sum = 5
    
    let addTwo = add(2)
    let xs = 1...100
    let x = xs.map(addTwo) // x = [3, 4, 5, 6, etc]
    

    这个看起来已经不错了,但是我们通过泛型可以让这个函数更加通用一点。

    func curry<A, B, C>(f: (A, B) -> C) -> (A -> (B -> C)) {
      return { a: A in
                { b: B in
                  return f(a, b) // returns C
                }
             }
    }
    

    在Swift中,可以最终简化成这样:

    func curry<A, B, C>(f: (A, B) -> C) -> A -> B -> C {
      return { a in { b in f(a, b) } }
    }
    

    那么讲了那么多,这东西有什么用呢?
    假设有这样一个需求,我需要记录某个系统的日志,日志需要包含以下几个要素:
    操作人的名字name,时间time,日志类型type和日志内容msg。
    一般可能会写这样的函数来调用:

    func createLogInfo(name:String, time:String, type:String, msg:String) ->String{
        return "name : \(name)\n" + "type : \(time)\n" + ("message : \(msg)\n" +"time: \(time)    ")
    }
    
    let log = createLogInfo("Noah", time: "today", type: "Info", msg: "Today's weather is good")
    

    现在我觉得每次都要传那么多参数很麻烦,对于我自己写的日志,我希望有一个简单一点的方法:

    func createMyLogInfo(time:String, type:String, msg:String) ->String{
        return createLogInfo("Noah", time: time, type: type, msg: msg)
    }
    

    我希望有一个只写Info类型日志的方法来简化操作:

    func createInfoLog(name:String, time:String, msg:String) ->String{
        return createLogInfo(name, time: time, type: "Info", msg: msg)
    }
    

    这样我每次都要写一个函数来简化我的操作。
    如果使用柯里化:

    func curryFour<A,B,C,D,E>(f:(A,B,C,D)->E) ->A->B->C->D->E{
        return {
            a in {b in {c in {d in f(a,b,c,d) }}}
        }
    }
    let curriedLogInfo = curryFour(createLogInfo)
    curriedLogInfo("Noah")("Today")("Error")("SometingWrong")
    let curriedMyLog = curriedLogInfo("Noah")
    curriedMyLog("Tmr")("Info")("SomeInfo")
    let curriedMyTodayLog = curriedLogInfo("Noah")("Today")
    curriedMyTodayLog("Warining")("FBI")
    

    总结:
    现在我们重新再来看一下柯里化的定义:
    在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

    关键点就在于能够把函数作为参数,并且可以把函数作为返回值来使用。
    所以函数柯里化实际上应该是“函数作为一等公民”这个特性所带来的附加的属性。就像数学里面能够从公理推导出定理一样。
    函数柯里化的作用,我现在感受到的主要是能够方便地把一些通用的函数通过柯里化这种技术变得更加地适用某些特殊的场景。
    相对地,反柯里化就是将函数变得拥有更强的普适性。

    相关文章

      网友评论

        本文标题:Swift Currying

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