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

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

// 一个小例子:

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

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

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

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

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

function Cook( 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); } );
耶,真方便!请注意我只是随手创建了个函数,甚至不用考虑怎么为它起名,只要拎着它的耳朵把它往一个函数里头一丢就可以了。
当你一想到作为参数的匿名函数,你也许想到对那些对数组里的每个元素进行相同操作的代码。

var a = [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]);
  }
常常要对数组里的所有元素做同一件事,因此你可以写个这样的函数来帮忙:

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

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

function sum(a){
      var s = 0;
      for (i = 0; i < a.length; i++)
          s += a[i];
      return s;
  }

function join(a){
      var s = "";
      for (i = 0; i < a.length; i++)
          s += a[i];
      return s;
  }

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

function reduce(fn, a, init){
      var s = init;
      for (i = 0; i < a.length; i++)
          s = fn( s, a[i] );
      return s;
  }

function sum(a){
      return reduce( function(a, b){ return a + b; }, a, 0 );
  }

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

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

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

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

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

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

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

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

你现在应该可以明白不久为何对那些对除了Java之外什么都沒被学过的计算机系学生表示不满了:( http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html) :

Without understanding functional programming, you can't invent MapReduce, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their 6.001-equivalent programming class that purely functional programs have no side effects and are thus trivially parallelizable. The very fact that Google invented MapReduce, and Microsoft didn't, says something about why Microsoft is still playing catch up trying to get basic search features to work, while Google has moved on to the next problem: building Skynet^H^H^H^H^H^H the world's largest massively parallel supercomputer. I don't think Microsoft completely understands just how far behind they are on that wave.

不理解函数式编程,你就发明不了MapReduce这个让Google的计算能力如此具有可扩展性的算法。Map和Reduce这两个术语源自Lisp语言和函数式编程……(这是另一篇文章的内容,emu也不是很理解其中的各种说法的来龙去脉,就不翻译了)

我希望你现在明白,把函数当成基本类型的(动态)编程语言能让你在编程过程中更好的进行抽象化,也就是使代码精悍、功能更内聚、更具可重用性及更具有扩展性。很多的Google应用使用Map/Reduce模式,因此一有人对其优化或修正缺陷,它们就都可以从中得益。

我准备要再罗嗦一下,我认为最有生产力的编程语言莫过于能让你在不同层次上都可以进行抽象化的。老掉牙的FORTRAN 语言以前是不让你写函数的注。C 有函数指针,可是它们都非常丑丑丑丑丑丑丑丑陋,不允许匿名声明,又不能在用它们时实现它们而偏偏要放在別处去实现。Java让你使用算子对象,一种更丑陋的东西。正如Steve Yegge所述,Java是個名词王国 (http://steveyegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)。

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

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

(0)

相关推荐

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

    有一天,你在浏览自己的代码,发现有两大段代码几乎一样.实际上,它们确实是一样的--除了一个关于意大利面(Spaghetti)而另一个关于巧克力慕思(Chocolate Moose). // 一个小例子: alert("偶要吃意大利面!");   alert("偶要吃巧克力慕思!"); 嗯,这个例子碰巧是用javascript写的,不过你就算不懂JavaScript,应该也能明白它在干什么. 拷贝代码不好.于是,你创建了个函数 function SwedishChef

  • Python的设计模式编程入门指南

    有没有想过设计模式到底是什么?通过本文可以看到设计模式为什么这么重要,通过几个Python的示例展示为什么需要设计模式,以及如何使用. 设计模式是什么? 设计模式是经过总结.优化的,对我们经常会碰到的一些编程问题的可重用解决方案.一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码.反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板.设计模式不会绑定具体的编程语言.一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性).最为重要的是,设计模

  • C++高级程序员成长之路

    C++这门语言从诞生到今天已经经历了将近30个年头.不可否认,它的学习难度都比其它语言较高.而它的学习难度,主要来自于它的复杂性.现在C++的使用范围比以前已经少了很多,java.C#.python等语言在很多方面已经可以代替C++.但是也有很多地方是其他语言完全无法替代的,主要集中在需要运行效率比较高的行业,比如游戏.高效的服务器. 现在学习java.C#等语言的人数远远高于C++,主要是C++的入门门槛太高,可能学习了一段时间后还做不了什么东西,导致信心大受打击,进而放弃. 我想把我自己的经

  • 用Python实现斐波那契(Fibonacci)函数

    Fibonacci斐波那契数列,很简单,就是一个递归嘛,学任何编程语言可能都会做一下这个. 最近在玩Python,在粗略的看了一下Learning Python和Core Python之后,偶然发现网上有个帖子Python程序员的进化写的很有意思.于是打算仿照一篇,那篇帖子用了十余种方法完成一个阶乘函数,我在这里会用九种不同的风格写出一个Fibonacci函数. 要求很简单,输入n,输出第n个Fibonacci数,n为正整数 下面是这九种不同的风格: 1)第一次写程序的Python程序员: de

  • Python numpy和matlab的几点差异介绍

    目录 numpy和matlab的几点差异 1.Numpy数组索引指定开始和结束时 2.Numpy.ndarray切片的修改会引起原矩阵的修改 3.numpy使用切片索引(例如1:2)不会产生降维 4.不同于matlab 5.不同matlab对于矩阵预算要求大小一致 python与matlab的优缺点 1.python的优势 2.matlab的优势 3.两者的区别 4.怎样选择 numpy和matlab的几点差异 Python numpy和matlab都是便捷灵活的科学计算语言,两者具有很多相似之

  • Golang基于JWT与Casbin身份验证授权实例详解

    目录 JWT Header Payload Signature JWT的优势 JWT的使用场景 Casbin Casbin可以做什么 Casbin不可以做什么 Casbin的工作原理 实践 登录接口请求 Token实现 使用Redis存储Token信息 用Casbin做授权管理 实现Casbin的策略 创建Todo JWT JSON Web Toekn(JWT)是一个开放标准RFC 7519,以JSON的方式进行通信,是目前最流行的一种身份验证方式之一. eyJhbGciOiJIUzI1NiIs

  • 学习哪门编程语言最有前途,最好赚钱,需求量高

    回答者卡特·佩基(Carter Page),谷歌(Google)工程经理 我的答案很短,但是由于这是一个需要站队的讨论,所以我先要介绍一些背景. 我聘用过许多软件工程师,他们身上最有价值的技能就是学习并迅速掌握一门编程语言的能力. 几年前,有一家境外公司为我们提供Java程序,他们的质量一直不太稳定.我可以通过翻阅简历来打造一个远程团队,但是我没法和他们进行面试,因为他们不会说英语.不过这种限制倒是带来了一个有趣的实验. 这些程序员的简历看起来很不错,都有着几年的工作经历.但是,他们写的代码依然

  • 什么是Perl?编程语言Perl详细介绍

    Perl 最初的设计者为拉里·沃尔(Larry Wall),它于1987年12月18日发表.Perl借取了C.sed.awk.shell scripting以及很多其他程序语言的特性.Larry Wall在新闻组comp.sources.misc发布了Perl脚本语言1.0版,当时他是Unisys公司的一名程序员.Perl借鉴了sh.Awk和Sed等脚本语言的特性,试图成为一个能简化报告处理的通用 Unix脚本语言. Perl 2在1988年发布,增加了更多特性,拥有更好的正则表达式引擎.Per

  • 注意!PHP 7中不要做的10件事

    切记,在PHP 7中不要做的10件事 1. 不要使用 mysql_ 函数 这一天终于来了,从此你不仅仅"不应该"使用mysql_函数.PHP 7 已经把它们从核心中全部移除了,也就是说你需要迁移到好得多的mysqli_函数,或者更灵活的 PDO 实现. 2. 不要编写垃圾代码  这一条可能易于理解,但是会变得越来越重要,因为 PHP 7 的速度提升可能会隐藏你的一些问题.不要仅仅满足于你的站点速度,因为迁移到 PHP 7 才让它变快. 为了理解速度有多重要,以及如何把事情做得更好,请看

  • 编程趣事:当下流行编程语言的”讨厌”程度排行榜

    为了不引起编程人员的误解和开发语言之争,小编先声明一下,这个榜单统计的是跨语言开发者最讨厌的编程语言,主要讨论的是web开发,比如你最喜欢用php语言来编程,那么可能会对其他语言的某些特性看不惯.这只是每个编程开发人员就自己喜好的程度个人略带主观的看法,上榜的语言不代表这个语言不好,只是说明某些WEB开发者不太喜欢这个语言中的某些特性而已.欢迎在评论中说出你对某些语言的看法. 下面这个排名是根据 Quora.Stack Overflow和Hacker News上相关帖子统计出来的.并用倒序排序~

随机推荐