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

作者:周思博(Joel Spolsky)
属于Joel on Software, http://www.joelonsoftware.com

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

有一天,你在浏览自己的程式码,发现有两大段程式码几乎一样。实际上它们的确一样,除了一个关于「Spaghetti」而另一个关于「Chocolate Moose」。

   // A trivial example:

   alert("I'd like some Spaghetti!");
   alert("I'd like some Chocolate Moose!");

这例子看来是JavaScript的,不过你就算不懂JavaScript,也应该明白在干什么。

重覆的程式码是个问题。于是,你建立函数

   function SwedishChef( food )
   {
       alert("I'd like some " + food + "!");
   }



   SwedishChef("Spaghetti");
   SwedishChef("Chocolate Moose");

01BorkBorkBork.PNG

嗯,这个例子很经典,但你能想到一个更深入的例子。这段程式码的优胜之处有很多,你听过上千次的:可维护性、可读性、抽象性= 好!

现在你留意到有另外两段程式码一模一样,除了一个反覆呼叫一个叫BoomBoom的函数,另一个反覆呼叫一个唤作PutInPot的。除此之外,这两段程式码真的像孖生的。

   alert("get the lobster");
   PutInPot("lobster");
   PutInPot("water");



   alert("get the chicken");
   BoomBoom("chicken");
   BoomBoom("coconut");

现在你需要一个办法,使得你可以将一个函数用作另一个函数的参数。这是个重要的能力,因为你更易将常用的程式码收藏在一个函数内。

   function Cook( i1, i2, f )
   {
       alert("get the " + i1);
       f(i1);
       f(i2);
   }



   Cook( "lobster", "water", PutInPot );
   Cook( "chicken", "coconut", BoomBoom );

看!我们成功将函数用作参数了。

你的编程语言能办到吗?

等等,假设你未定义PutInPot或BoomBoom这些函数。如果能直接将它写进一行内,不是比在其他地方宣告它们更好吗?

   Cook( "lobster",
         "water",
         function(x) { alert("pot " + x); } );
   Cook( "chicken",
         "coconut",
         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要求你建立一个有单method的物件,称之为functor。另外许多物件导向语言要求你为每件class都建立一个档案,结果变得不怎么快(klunky fast)。如果你的编程语言要求使用functor,就不能彻底得到现代编程环境的好处。看看你可否退货拿回些钱。

不过写出那些仅仅只是对阵列中每个元素做事的小小函数,究竟能得到多少好处嘱?

让我们回到’map’函数。对阵列内的每个元素做事时,很可能并不在乎哪个元素先做。无论由第一个还是最后一个元素开始,结果都是一样的,对不对?如果你手头上有2个CPU,就可以写段程式码,使得它们各对一半的元素工作,于是’map’就变快两倍了。

或者你在全球有千千百百台伺服器(只是假设),还有一个很大很大的阵列,存放整个互联网的内容(同样也只是假设)。现在你可以在这些伺服器上执行’map’,让各台伺服器只处理问题很小的一部份。

所以现在可以再举个例子,要写出能超快速搜寻整个互联网的程式码其实很简单,只要呼叫一个以基本字串搜寻器作为参数的’map’函数即可。

这里头有件真正有趣的事值得注意:当你把’map’和’reduce’想成每个人都能用的函数,而大家也都在用,只要有个超级天才,写出能在全球巨型平行电脑阵列上执行’map’和’reduce’的程式码,那么所有原本用单一回圈能正常运行的旧程式码,还是照样能用,但是却会快上千万倍,也就是说可以用来在瞬间解决掉巨大的问题。

Lemme重覆了这一点。它把回圈的基本概念抽象出来,你可以用任何所要的方式实作回圈,其中包括能适切地配合额外硬体的实作方式。

你现在明白我之前写到不满那些除了Java之外什么都没被教过的电脑科学学生

 不了解functional programming就无法发明[MapReduce](http://labs.google.com/papers/mapreduce.html)这个让Google延展性如此强大的演算法。Map和Reduce这个术语源自Lisp和functional programming。回想起来,对还记得6.001或等同程式课的人来说MapReduce实在是很明显的事情,纯粹的functional programs 没有副作用,所以能轻易地平行化。

Google发明了MapReduce而微软没有,这个事实在某方面解释以下的现况:当微软还在努力让基本搜寻功能会动时,Google已经进入下一个问题,建立Skynet这个世界上最大的大规模平行运算超级电脑。我不认为微软真的了解他们在这一波风潮落后了多少。

我希望你现在明白,有第一级函数的编程语言让你找到更多抽象化的机会,也就是说你程式码会更小、更紧密、更便于再用而且延展性更佳。无数的Google应用软体使用MapReduce,因此一有人改进其效率或修正臭虫,这些应用软体都得益了。

现在我有一点点激动了,我要说最有生产效益的编程环境,莫过于能让你在不同的抽象层次作业的环境。老掉牙的GW- BASIC不让你写函数注。C有函数指标,但是丑陋之极又不许匿名,一定得在其他地方实作,不能直接写在使用的地方。Java则是让你使用functor这个更丑陋的东西。正如Steve Yegge所述,Java是个[名词王国](http://steve-yegge.blogspot.com/2006/03/execution-in- kingdom-of-nouns.html)。


注:作者原文写了FORTRAN,他已作出更正启示。他对上一次使用FORTRAN是27年前,当时的FORTRAN也有函数。

这些网页的内容为表达个人意见。
All contents Copyright © 1999-2006 by Joel Spolsky. All Rights Reserved.