在Mostly Adequate Guide drboolean中,“声明性编码适用于并行计算”。请看以下示例:
// imperative
var authenticate = function(form) {
var user = toUser(form);
return logIn(user);
};
// declarative
var authenticate = compose(logIn, toUser);
我认为订单保留在撰写(从右到左)。那么声明性编码为并行计算的目的提供了哪些附加功能呢?
我同意你的订单,至少在authenticate
的例子中必须保留它。
关于可能使声明性更加平行友好的范例的功能,它完全是关于模块化的。你可以在第一个例子(车辆之一)中看到它更好:
// imperative
var makes = [];
for (var i = 0; i < cars.length; i++) {
makes.push(cars[i].make);
}
// declarative
var makes = cars.map(function(car) { return car.make; });
这里,声明性版本是用一个可以异步运行的单独函数编写的,而在命令式版本中,你只需要将一些代码与其余代码混合在一起。
声明性设计使得更容易将一些部分包装在不同的进程中。
我认为该指南在这一点上有点误导,在本声明之前使用compose
作为例子。
在compose
的情况下,声明式样式将不允许并行计算,因为即使在使用compose
时,toUser
仍然必须在logIn
之前执行,因为它取决于它的输出!
我认为map
示例更适合说明这一点。虽然你必须逐步执行for循环,因为你真的不知道下一步会是什么样子,如果你没有完成前一步,你就不能真正完成所有步骤一旦。另一方面,当你使用map
时,你会给它一个纯函数,它在集合的每个项目上执行。该函数是一个原子动作,它的执行不依赖于函数之前或之后的执行。因此,您可以并行执行所有操作。
但是在指南中也提到的一个重要的补充,但是从你的引用中删除,是函数必须是纯的(意味着它们不会改变它们“操作”的数据结构) ,而是返回一个新的)。一个很好的例子是:
// impure
var impureSquared = function (list) {
for (var i = 0; i < list.length; i++) {
list[i] = list[i] * list[i];
}
}
// pure
var pureSquared = function (list) {
return list.map(function (item) {
return item * item;
}
}
如果你要在两个并行线程中的同一数据结构上运行impure函数,你可能无法得到你期望的结果,因为数据结构在每个线程中被这个函数改变了两次。另一方面,如果你对纯函数做同样的事情,两个线程都会得到预期的结果而不会相互干扰。
我认为纯函数执行原子操作的概念实际上对并行执行比声明编程更重要。你只是偶然地做到这一点,同时以声明式的方式编码。
所有这一切,我应该补充一点,javascript通常是一种单线程语言,所以当你谈论并行计算时,它并不是真正的语言;)