Swift07/90Days - 柯里化

Swift 中的柯里化

新手上路的学习笔记,若有错误还望指出,不胜感激。javascript

上集:理论预备

在学习柯里化的过程当中接触到了三个有趣的概念,在此和各位分享一下。html

偏函数 (Partial Function)

偏函数是只对函数定义域的一个子集进行定义的函数,是一个数学概念。java

偏函数定义以下:ios

从输入值集合 X 到可能的输出值集合 Y 的函数 f (记做f:X→Y) 是 X 和 Y 的关系,若 f 知足多个输入能够映射到一个输出,但一个输入不能映射到多个输出,则为偏函数。编程

换句话说,定义域 X 中可能存在某些值,在值域 Y 中没有对应的值。从定义来看,偏函数是函数的超集。也就是说,函数都是偏函数,但偏函数不都是函数。swift

偏函数应用 (Partial Application || Partial Function Application)

上面说的概念是数学中的概念,和咱们将要接触的内容无关。和咱们关系比较大的是偏函数应用 (Partial Application)。app

偏函数应用的定义以下:ide

在计算机科学领域,偏函数应用是指经过固定原函数的一部分参数生成新函数的过程,新函数的参数数目少于原函数。函数

经过数学公式演示一下,好比原函数 f 有三个参数: f:(X×Y×Z)→N ,经过绑定第一个参数 X ,咱们能够获得一个新的函数: partial(f):(Y×Z)→N学习

编程中的偏函数,好比廖雪峰老师在偏函数中说起的 functools 模块的用法,其实就是偏函数应用,又叫局部函数应用,有道词典翻译为切一刀在此表过不提。

为避免混淆,下文中说起的偏函数均为编程中的偏函数应用 (Partial Application)。

偏函数有点像是给函数的参数设置默认值同样,不过偏函数更加灵活。好比下面这段 c 语言的代码中,foo23 函数就是 foo 函数的偏函数,参数 b 的值被绑定为 23

int foo(int a, int b, int c) {
  return a + b + c;
}

int foo23(int a, int c) {
  return foo(a, 23, c);
}

柯里化

柯里化和偏函数有些关系,可是是两个彻底不一样的概念。

柯里化定义以下:

柯里化(英语:Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。

经过数学公式来简单的演示一下,原始的函数是一个普通的函数:f:(X×Y) → Z ,柯里化以后函数变成了:curry(f):X → (Y→Z)

咱们用几段 js 代码演示一下柯里化的过程。首先先看一个普通的函数 foo ,返回参数的平方:

var foo = function(a) {
    return a * a;
}

假设咱们的函数都只能有一个参数,那么能够用下面的方式模拟出一个多参数函数:

var foo = function(a) {
    return function(b) {
        return a * a + b * b;
    }
}

咱们能够这样调用 foo(3)(4)

柯里化解决的问题是:将多参数函数转变为一系列单参数函数的链式调用。

柯里化背后的基本想法是函数能够局部应用,意思是一些参数值能够在函数调用以前被指定,而且返回一个新的函数。也就是偏函数的基本思想。

小结

能够看到,柯里化将函数进行了~~肢解~~拆分,这样咱们能够很容易的实现偏函数。好比:

var foo = function(a) {
    return function(b) {
        return a * a + b * b;
    }
}
var foo3 = foo(3);
foo3(4);

这就出来了。函数 foo3 就是 foo 函数的偏函数。

简单总结一下偏函数和柯里化以及二者的关系:

  • 偏函数应用:固定原函数的几个参数值,从而获得一个新的函数。
  • 函数柯里化:一种使用匿名单参数函数来实现多参数函数的方法。
  • 关系:柯里化可以轻松的实现某些偏函数应用。

中集:柯里化与偏函数

前面扯了不少有的没的,接下来咱们来看看在 Swift 中柯里化。

节目预告:下面的例子极为简单,让各位见笑了。但愿经过这些最简单的例子演示柯里化最关键的部分。

传统手段:老老实实传参数

咱们先来写个最简单的东西好了:计数器,每次运算后+1便可:

func add(a:Int) -> Int{
    return a + 1;
}

var a = 0

a = add(a)   // 1
a = add(a)   // 2

可是每次都+1,步子有点小了。有时咱们可能也须要+2,+3,+4,那么简单,咱们把须要加的步长放在参数里就行:

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

var a = 0

a = add(a, 1)   // 1
a = add(a, 2)   // 3

彷佛没什么问题。那么问题来了:我大部分时间都只要+1啊,我可能只是100次调用有90次要+1,为了剩下的那10次,每行代码都要多个参数,是否是麻烦了点?OK嫌麻烦咱们能够经过设置默认值的方式解决。

进阶手段:能够设置默认值

在函数定义的时候咱们就给它定好默认值是1,那不就得了:

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

var a = 0

a = add(a)   // 1
a = add(a, b:2)   // 3

若是你要+1,行,你别写参数我给你调好了自动+1;若是你要+2+3,行,爱加几加几,本身写到参数里去。

彷佛没什么问题。那么问题来了:我有一半的时间要+1,有一半的时间要+2,还有些时间+3+4,怎么玩?

唉可真是磨人的小妖精。

柯学手段:经过柯里化实现

和前面的例子不同的是,柯里化的函数返回一个新的函数而不是计算后的结果。计算后的结果要经过返回的新函数计算得到:

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

let addOne = add(1)
let addTwo = add(2)

var a = 0

a = addOne(a: a)    // 1
a = addTwo(a: a)    // 3

在这个例子里,咱们生成两个新的函数:addOneaddTwo 分别进行+1和+2操做。能够看到,经过柯里化实现偏函数是十分方便的,一切都顺其天然,水到渠成。

经过这个例子能够看出,柯里化是偏函数的方法论,偏函数是柯里化背后的~~男人~~思想。

下集:实例方法与柯里化

实际上,Swift 中的实例方法也是柯里化方法,你能够传个实例做为第一个参数。

咱们还就和计数器杠上了,再次以计数器为例:

class Counter {
    var b: Int = 1

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

咱们能够初始化实例对象而后来调用:

let counter = Counter()
var a = counter.add(1)  // 2

咱们也能够这样作:

let add = Counter.add   // Function
let counter = Counter()
var a = add(counter)(1) // 2

这两个是彻底等价的。

有点神奇啊,不是吗?注意, let add = Counter.add 这个定义后面没有小括号。也就是说,咱们并无调用它,只是指向它,像一个指针同样指了过去。

咱们能够按住 option 键查看一下 add 的类型:let add: Counter -> (Int) -> Int 。它的参数是一个 Counter 类型的实例,返回的是另外一个新函数,这个新函数的参数类型是 Int ,返回的类型也是 Int ,和类里定义的函数类型是同样的。

看起来好像挺有趣的,不过问题来了:有啥用呢?

这个问题我还没办法回答,由于我也是刚刚接触这部份内容。我只能作一名知识的搬运工了。

Instance Methods are Curried Functions in Swift 这篇文章里,做者举了一个例子:用 Target-Action 模式实现一个 Control

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: (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 MyViewController {
    let button = Control()

    func viewDidLoad() {
        button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
    }

    func onButtonTap() {
        println("Button was tapped")
    }

下集的下集:其余

因为没有在实战中用过,暂时不对柯里化作太多评价。就目前的了解来看,它可让咱们很方便的对函数进行局部调用,让代码更加灵活,语意更加清晰。

若是想感觉一下原汁原味的柯里化,不妨接触一下 Haskell 这门语言。 Haskell 中多参数函数都是经过柯里化实现的。它的做者是 Haskell Brooks Curry,是的就是柯里化 (Curring) 的命名者 (不过并非他发明的)。

使用 Mac 的朋友能够下载 Haskell Platform for Mac ,而后经过 ghci 命令试一试 Haskell 的代码。

固然也有反柯里化,感兴趣的同窗能够继续了解一下:)


References

相关文章
相关标签/搜索