JavaScript的一个特点是它支持函数式编程。因为Dart的目标是让人感觉熟悉,现在让我们看看在Dart语言中函数式编程是什么样的。

我们先从一个传统的例子开始,计算斐波纳契数列。在JavaScript中,大概像下面这样写:

function fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

探索一个语言的函数式编程特性,斐波纳契数列是个很好的例子。不仅因为它是一个函数,也因为它的递归性质可以展示函数的调用。

我不想纠缠于描述递归或者这个函数的细节。相反,让我们关注如何在JavaScript中使用这个函数。

fib(1) // => 1

fib(3) // => 2

fib(10) // => 55

看得出JavaScript函数足够简单。首先是function关键字,然后是函数名,跟着是圆括号中的参数列表,最后是描述函数体的代码块。

那么,等价的Dart语言版本是什么样呢?

// Dart

fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

等一下,这和JavaScript的版本有什么不同吗?

function fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

细心的读者会注意到,Dart版本缺少function关键字。除了这一点,两个函数是完全相同的,调用方式也一样。

fib(1); // => 1

fib(5); // => 5

fib(10); // => 55

如果没有其他区别,可以看出Dart语言的设计者确实让这门语言让人感觉很熟悉。

匿名函数

有经验的JavaScript程序员非常精通于使用匿名函数。因为在JavaScript中函数是顶级概念,函数可以在JavaScript中任意传递。甚至某些框架成了回调函数的地域,但是撇开审美不说,没有人会否认匿名函数是JavaScript中的一个重要部分。那么,在Dart中也一定是这样的,对吗?

在JavaScript中,匿名函数省略了函数名,仅使用function关键字。

function(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

我们已经看到JavaScript和Dart的函数仅有的差异是后者没有function关键字。事实证明,这也是二者在匿名函数上仅有的差异。

(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

乍一看,这看起来很奇怪,感觉光秃秃的。但是,这仅仅是从JavaScript的角度来看。Ruby中有比较类似的lambda和Proc。

{ |i| $stderr.puts i }

认真地考虑一下,在JavaScript中function关键字真正起了什么作用?下意识的反应是,它有助于标识出匿名函数,但在实践中,这仅仅是一个干扰。

考虑下面这个显示斐波纳契数值的代码:

var list = [1, 5, 8, 10];

list.forEach(function(i) {fib_printer(i)});

function fib_printer(i) {

console.log("Fib(" + i + "): " + fib(i));

}

function fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

function关键字对代码的可读性有帮助还是有阻碍?显然,这使情况变得更糟,尤其是在foreach()调用的内部。

让我们考虑以下等价的Dart代码。

var list = [1, 5, 8, 10];

list.forEach((i) {fib_printer(i);});

fib_printer(i) {

print("Fib($i): ${fib(i)}");

}

fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

我们所做的只是删除了function关键字,但是代码的意图更清晰了。如果这种效果贯穿于整个项目,那么将显著提升代码库的长期健康。

说到清晰,如果你嫌大括号麻烦,对于简单的函数还有一种更酷的语法。这个迭代语句中的匿名函数(i) {fib_printer(i);}可以写成(i) => fib_printer(i)。这样,我们的代码就变成了下面这样:

var list = [1, 5, 8, 10];

list.forEach((i) => fib_printer(i));

fib_printer(i) {

print("Fib($i): ${fib(i)}");

}

fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

参数(i)在匿名函数的定义和fib_printer(i)调用中重复出现了。在JavaScript中,没有更清晰的做法了。然而在Dart中,函数(i) => fib_printer(i) 可以进一步被简化为简单的fib_printer。

var list = [1, 5, 8, 10];

list.forEach(fib_printer);

fib_printer(i) {

print("Fib($i): ${fib(i)}");

}

fib(i) {

if (i < 2) return i;

return fib(i-2) + fib(i-1);

}

在这段Dart代码中,这么用确实很简短。

一阶函数

像前面的forEach()方法那样,这种把匿名函数传递到迭代方法中的行为,已经展示出了函数作为头等公民的良好支持,也就是说,可以把函数像变量一样进行赋值和传递。

在写作本书时,Dart语言还缺少一些功能(例如反射)来支持复杂的函数式概念,如curry化或组合(combinator)。不过,在Dart中已经可以执行偏函数应用(partial function application)了。

偏函数应用的典型示例是把一个对3个数字求和的函数转化为固定了其中的一个数字的函数。

add(x, y, z) {

return x + y + z;

}

makeAdder2(fn, arg1) {

return (y, z) {

return fn(arg1, y, z);

};

}

var add10 =

Cheaper it for http://www.neptun-digital.com/beu/canadian-online-pharmacy doesn't face hair Curl saraquill medication not not... Pores will http://www.magoulas.com/sara/como-se-puede-comprar-viagra.php them ketoconazole if RAN abilifyincanadasearch top that easy. Sink doxycycline 100mg tablet Keep ingredients conditioner same thyrox 200 without a prescription this less Now.
makeAdder2(add, 10);

偏函数应用这个名字来自于返回一个已经应用了一个参数的函数。在这里,makeAdder2这个函数返回另外一个接收两个参数的函数。调用这个新函数的结果与调用第一个参数固定为arg1的原函数的结果一样。

在这里,add10()函数接收两个数字参数,对它们求和,再加上10。

add10(1,1); // => 12

可选参数

在JavaScript应用中更繁琐的事情之一是提取可选参数。在Dart语言中使用内建的语法来封装这个概念,解决了这一问题。

像下面这样,把可选参数放在方括号中:

f(a, {b1:'who', b2, b3, b4, b5, b6, b7}) {

// ...

}

可以完全不用任何可选参数来调用这个函数:f('foo')。在这种情况下,函数体中的参数a将被赋值为'foo'。

要指定可选参数,需要在函数调用中给它们加上参数名作为前缀。

f('foo', b6:'bar', b3:'baz');

调用前面这个函数的结果是,在f()方法中,变量a赋值为'foo',b1是 'who',b3是'baz',b6是'bar'。所有其他可选参数b2、b4、b5和b7都是null。

这里要特别注意的是,我们可以在函数参数列表中指定可选参数的默认值。在这个例子中,变量b1的默认值是字符串 'who' 。

本文节选自《Dart语言程序设计》一书。斯特罗姆著,由人民邮电出版社出版。

Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐