你的编程语言可以这样做吗?

时间:2021-05-25

有一天,你在浏览自己的代码,发现有两大段代码几乎一样。实际上,它们确实是一样的——除了一个关于意大利面(Spaghetti)而另一个关于巧克力慕思(ChocolateMoose)。

//一个小例子:

alert("偶要吃意大利面!");
alert("偶要吃巧克力慕思!");
嗯,这个例子碰巧是用javascript写的,不过你就算不懂JavaScript,应该也能明白它在干什么。

拷贝代码不好。于是,你创建了个函数

functionSwedishChef(food){
alert("偶要吃"+food+"!");
}
SwedishChef("意大利面");
SwedishChef("巧克力慕思");
Ok,这只是一个很小很小的例子而已,相信你能想像到个更实际一点的例子。这段代码有很多优点,你全都听过几万次了:可维护性、可读性、抽象性=好!

现在你留意到有另外两段代码几乎跟它们一模一样,除了一个反复调用一个叫BoomBoom的函数,另一个反复调用一个叫PutInPot的。除此之外,這两段代码简直没什么两样:

alert("拿龙虾");
PutInPot("龙虾");
PutInPot("水");
alert("拿鸡肉");
BoomBoom("鸡肉");
BoomBoom("椰子酱");
现在要想个办法,使得你可以將一个函数用作另一个函数的参数。这是个重要的能力,因为你更容易将框架代码写成一个函数(emu注:还记得templatemethod模式吧?)。

functionCook(i1,i2,f){
alert("拿"+i1);
f(i1);
f(i2);
}
Cook("龙虾","水",PutInPot);
Cook("鸡肉","椰子酱",BoomBoom);
看看,我们居然把函数当成调用参数传递了!

你的编程语言能办到吗?

等等……假如我们已经有了PutInPot和BoomBoom这些函数的具体实现代码(而且又不需要在别的地方重用它们),那么用内联语法把它们写进函数调用里面不是比显式的声明这两个函数更漂亮吗?

Cook("龙虾",
"水",
function(x){alert("pot"+x);});
Cook("鸡肉",
"椰子酱",
function(x){alert("boom"+x);});
耶,真方便!请注意我只是随手创建了个函数,甚至不用考虑怎么为它起名,只要拎着它的耳朵把它往一个函数里头一丢就可以了。
当你一想到作为参数的匿名函数,你也许想到对那些对数组里的每个元素进行相同操作的代码。

vara=[1,2,3];
for(i=0;i<a.length;i++){
a[i]=a[i]*2;
}
for(i=0;i<a.length;i++){
alert(a[i]);
}
常常要对数组里的所有元素做同一件事,因此你可以写个这样的函数来帮忙:

functionmap(fn,a){
for(i=0;i<a.length;i++){
a[i]=fn(a[i]);
}
}
现在你可以把上面的东西改成:

map(function(x){returnx*2;},a);
map(alert,a);
另一个常见的任务是将数组内的所有元素按照某总方式汇总起来:

functionsum(a){
vars=0;
for(i=0;i<a.length;i++)
s+=a[i];
returns;
}

functionjoin(a){
vars="";
for(i=0;i<a.length;i++)
s+=a[i];
returns;
}

alert(sum([1,2,3]));
alert(join(["a","b","c"]));
sum和join长得很像,你也许想把它们抽象为一个将数组内的所有元素按某种算法汇总起來的泛型函数:

functionreduce(fn,a,init){
vars=init;
for(i=0;i<a.length;i++)
s=fn(s,a[i]);
returns;
}

functionsum(a){
returnreduce(function(a,b){returna+b;},a,0);
}

functionjoin(a){
returnreduce(function(a,b){returna+b;},a,"");
}
许多早期的编程语言没法子做这种事。有些语言容许你做,却又困难重重(例如C有函数指针,但你要在別处声明和定义函数)。面向对象语言也不确保你用函数可以干些啥(把函数当对象处理?)。

如果你想将函数视为一类对象,Java要求你建立一个有单方法的对象,称为算子对象。许多面向对象语言要你为每个类都建立一个完整文件,像这样开发可真叫快。如果你的编程語言要你使用算子对象来包装方法(而不是把方法本身当成对象),你就不能徹底得到现代(动态)编程语言的好处。不妨试试看你可否退货拿回些钱?

不用再写那些除了经过一个数组对每个元素做一些事情之外一无是处的函数,有什么好处?

让我们看回map函数。当你要对数组内的每个元素做一些事,你很可能不在乎哪个元素先做。无论由第一个元素开始执行,还是是由最后一个元素执行,你的结果都是一样的,对不?如果你手头上有2個CPU,你可以写段代码,使得它们各对一半的元素工作,于是乎map快了两倍。

或者,发挥一下想像力,设想你在全球有千千万万台服务器分布在全世界的若干个数据中心,你有一个真的很大很大的数组,嗯,再发挥一下想像力,设想这个数组记录有整个互联网的内容。还了,现在你可以在几千台服务器上同时执行map,让每台服务器都来解决同一个问题的一小部分。

那么在这个例子里面,编写一段非常快的代码来搜索整个互联网这个问题,其实就和用一个简单的字符串搜索器(算子)作为参数来调用map函数一样简单了。


希望你注意到一个真正有意思的要点,如果你想要把map/reduce模式变成一个对所有人都有用,对所有人都能立刻派上用场的技术,你只需要一个超级天才来写最重要的一部分代码,来让map/reduce可以在一个巨大的并行计算机阵列上运行,然后其他旧的但是一向在单一个循环中运行良好的代码,仍可以保持正确的运行,惟一的差别只是比原来单机运行快了n倍。这意味着它们都一不留神突然变成可以被用来解决一个巨大的问题的代码。

让我再啰嗦一下,通过把“循环”这个概念加以抽象,你可以把用任何你喜欢的方式来实现“循环”过程,包括可以实现让循环迭代速度随着硬件计算能力保持令人满意的同步增长。

你现在应该可以明白不久为何对那些对除了Java之外什么都沒被学过的计算机系学生表示不满了:(http:///2006/03/execution-in-kingdom-of-nouns.html)。

作者注:这里提起了FORTRAN,不过我上次使用FORTRAN是27年前的事了。FORTRAN是有函数的,我码字那会儿脑子里面想的大概是GW-BASIC语言。(emu注,basic确实只有所谓的子程序和go-sub语句,作用只是重新组织代码结构而已,没有参数和调用堆栈,因此没有真正的函数调用)

译者注:原作者起了《你的编程语言可以这样做吗》这个标题其实并不是这篇文章的真正价值所在,我转这篇文章也不是因为原作者可以把语言的初级技巧玩得转,而是因为这是一篇map/reduce模型的示范。

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章