这一次来学习一下Partial Application。我们先看一下函数的介绍,在维基上有简单的介绍:
在数学中,一个函数是描述每个输入值对应唯一输出值的这种对应关系,符号为 f(x)。例如,
表达式 f(x)=x2表示了一个函数 f,其中每个输入值x都与唯一输出值x2相联系。因此,如果一个输入值为3,那么它所对应的输出值为9。而g(x,y) = xy有两个参量x和y,以乘积xy为值。
上面描述了函数(为方便假设x,y都是int),并且给出了函数的两个例子,先换一种方式来看,f(x)可以表示为:x -> y(x2),即经经过f到x2的映射,写成 int -> int。接受一个int 返回一个int。再看g(x,y)可以表示为:x -> y -> z(xy)。即x,y经过g的映射到z,写成 int -> int -> int。我们看g(x,y)函数,用javascript来实现一下:
function g(x,y){ return x*y; }
很完美啊,很接近数学定义。它依次接受两个参数,x与y。并且返回它们两个的乘积。但是当x是个常数,比如x=n(n是一个自然数)。那么g(n,y)=ny。这就变成一个常数与一个变量的乘积,它接受一个参数y返回ny,即y -> z(ny) 的映射,写成 int -> int。因此,我们可以这样来理解上面的工作,g(x,y)是接受一个参数int,并且返回一个函数 int ->int 。这个返回的函数只接受一个int 并且返回一个int。来用javascript表示一下:
var h = g(2);
这里的h表示函数h(y)=2y。这样就有h(5)=10,h(13)=26等。
h(5);
h(13);
这个技术是把需要多个参数的函数形式转变为接受单个参数的函数链,它通常叫做Curring,这是为了纪念Haskell Curry而起的名字,但他并不是第一个提出的1。但是很遗憾的是javascript并不支持这样的特性。所以要实现这样的特性需要做一些工作,这些工作并不复杂。主要是把参数存储起来,等待调用函数链上的下一个函数时拿出前边参数继续传递给链上的下一个函数,直到最后得到返回值。先看一下下面的代码:
1 function atarr(a,index){ 2 var index=index||0,args = new Array(a.length - index); 3 for(var i in a){ 4 if(i>=index) args[i-index]=a[i]; 5 } 6 return args; 7 } 8 function m(scope,fn){ 9 if(arguments.length<3) return fn.call(scope); 10 var p = atarr(arguments,2); 11 12 return function(){ 13 var args = atarr(arguments); 14 return fn.apply(scope,p.concat(args)); 15 } 16 }
测试代码:
1 var plus = function(a,b){ 2 return a+b; 3 }; 4 var plus2 = m(null,plus,2); 5 console.log(plus2(10)); 6 console.log(plus2(0)); 7 8 //结果 9 12 10 2
这样我们的目标已经实现啦。在上面的atarr函数是将arguments对象中指定位置开始的参数取出并且保存到一个数组中。m函数就是主角,它完成了前面定义的任务,实现了保存函数链上的参数并且返接受余下参数的函数。测试代码中的plus函数原先接受a,b两个参数并返回a与b之和,即 int -> int -> int,而plus2则变成了接受一个参数b与2相加,并返回2与b之和,即 int -> int。
通过上面的一些工作,我们实现了javascript中的Partial Application,在dojo框架中hitch2实现了域绑定和partial。有兴趣可以读一下它的源码,也是非常简单明了的。
1.Curring:来自维基百科 Curring。
2.来自dojo toolkit,参见hitch。