新手上路的学习笔记,若有错误还望指出,不胜感激。javascript
在学习柯里化的过程当中接触到了三个有趣的概念,在此和各位分享一下。html
偏函数是只对函数定义域的一个子集进行定义的函数,是一个数学概念。java
偏函数定义以下:ios
从输入值集合 X 到可能的输出值集合 Y 的函数 f (记做f:X→Y) 是 X 和 Y 的关系,若 f 知足多个输入能够映射到一个输出,但一个输入不能映射到多个输出,则为偏函数。编程
换句话说,定义域 X
中可能存在某些值,在值域 Y
中没有对应的值。从定义来看,偏函数是函数的超集。也就是说,函数都是偏函数,但偏函数不都是函数。swift
上面说的概念是数学中的概念,和咱们将要接触的内容无关。和咱们关系比较大的是偏函数应用 (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
在这个例子里,咱们生成两个新的函数:addOne
和 addTwo
分别进行+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 的代码。
固然也有反柯里化,感兴趣的同窗能够继续了解一下:)