Make great things

What I cannot create, I do not understand.

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

你使用的编程语言能这样做吗?——有关匿名函数的介绍

翻译自(http://joelonsoftware.com/items/2006/08/01.html),英语好的直接看原版的吧。

一天当你浏览你写过的代码时,你发现有两个地方的代码看起来是一样的。事实上,它们基本上就是一样的,除了一处是“Spaghetti”,另外一处是“Chocolate Moose”。

    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");

虽然这个例子没什么实际作用,但这份代码看起来更好,它更容易维护、容易阅读、更加抽象。

下面一个例子中的代码看起来也差不多一样,除了一个调用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中,如果你要将函数看成是一个类对象的话,就必须创建一个只包含一个叫做functor方法的对象。而很多面向对象语言要求你为每一个类创建一个文件,如果你确定要这样做,那就不能享受到匿名函数带来的好处了。

对于仅仅迭代数组中每个元素并进行处理,使用匿名函数能带来什么好处呢?

我们来重新看一下map函数。当你需要对数组中每个元素操作时,实际上并不需要对它们按顺序处理。你可以从前到后,或者按相反顺序。如果你的电脑有两个cpu,你可以数组中一般的元素分别给一个cpu处理,这样你的map就能快一倍了。

又或者,(仅仅是理论上的)你有成千上百个服务器分布在世界各处的数据中心,同时你有一个超级大的数组,里面包含了(理论上的)互联网上所有的数据,你就可以在你的服务器上运行map函数,每个服务器只处理一小部分的问题。

例如,将一个字符串查找函数作为参数传入map函数,你就可以很快地搜索互联网上的所有内容。

一旦你意识到,可以让任何人使用map和reduce函数,并且只需要有一个天才能将map和reduce函数运行在世界各处的电脑上,而所有其他 已有的运行循环的函数都可以作为参数调用map和reduce,这样,原来一个十分复杂的问题,现在一眨眼的功夫就能得出结果。这就是说,将循环的概念抽 象出来,就能在任何地方实现循环,包括可以通过额外的硬件进行扩展。

希望你现在能明白,能将函数当作对象的语言可以给你更多的机会进行抽象,意味着你的代码可以更短,更紧凑,更容易复用和扩展。

 

 

posted on 2014-04-14 18:42  wbin91  阅读(298)  评论(0编辑  收藏  举报