31

我在聊天应用程序上使用node.js已经有一段时间了(我知道,非常原始,但我认为这将是一个很好的学习项目)。Underscore.js提供了很多看起来很有趣的函数式编程概念,所以我想了解如何在 JavaScript 中设置函数式程序。

根据我对函数式编程的理解(这可能是错误的),整个想法是避免副作用,这基本上是有一个函数可以更新函数之外的另一个变量,比如

var external;
function foo() {
   external = 'bar';
}
foo();

会产生副作用,对吗?因此,作为一般规则,您希望避免在全局范围内干扰变量。

好的,那么当您处理对象时,它是如何工作的呢?例如,很多时候,我会有一个构造函数和一个初始化对象的 init 方法,如下所示:

var Foo = function(initVars) {
   this.init(initVars);
}

Foo.prototype.init = function(initVars) {
   this.bar1 = initVars['bar1'];
   this.bar2 = initVars['bar2'];
   //....
}

var myFoo = new Foo({'bar1': '1', 'bar2': '2'});

所以我的 init 方法是故意造成副作用的,但是处理同样情况的功能性方法是什么?

此外,如果有人能指出我试图尽可能地发挥功能的程序的 Python 或 JavaScript 源代码,那也将不胜感激。我觉得我接近“得到它”,但我只是不完全在那里。主要是我对函数式编程如何与传统的 OOP 类概念一起工作感兴趣(或者如果是这样的话,就为了不同的东西而取消它)。

4

4 回答 4

31

你应该阅读这个问题:

Javascript 作为一种函数式语言

有很多有用的链接,包括:

现在,我的意见。很多人误解了 JavaScript,可能是因为它的语法看起来像大多数其他编程语言(Lisp/Haskell/OCaml 看起来完全不同)。JavaScript不是面向对象的,它实际上是一种基于原型的语言。它没有类或经典继承,因此不应该与 Java 或 C++ 进行比较。

JavaScript 比 Lisp 更好;它具有闭包和一流的功能。使用它们,您可以创建其他函数式编程技术,例如部分应用程序(currying)。

让我们举个例子(使用sys.puts来自 node.js):

var external;
function foo() {
    external = Math.random() * 1000;
}
foo();

sys.puts(external);

为了摆脱全局副作用,我们可以将它包装在一个闭包中:

(function() {
    var external;
    function foo() {
        external = Math.random() * 1000;
    }
    foo();

    sys.puts(external);
})();

请注意,我们实际上不能在范围内externalfoo范围外做任何事情。他们完全包裹在自己的封闭中,无法触及。

现在,要摆脱external副作用:

(function() {
    function foo() {
        return Math.random() * 1000;
    }

    sys.puts(foo());
})();

最后,该示例不是纯功能性的,因为它不可能。使用从全局状态读取的随机数(以获取种子)并打印到控制台是一种副作用。

我还想指出,将函数式编程与对象混合是非常好的。以此为例:

var Square = function(x, y, w, h) {
   this.x = x;
   this.y = y;
   this.w = w;
   this.h = h;
};

function getArea(square) {
    return square.w * square.h;
}

function sum(values) {
    var total = 0;

    values.forEach(function(value) {
        total += value;
    });

    return total;
}

sys.puts(sum([new Square(0, 0, 10, 10), new Square(5, 2, 30, 50), new Square(100, 40, 20, 19)].map(function(square) {
    return getArea(square);
})));

如您所见,在函数式语言中使用对象就可以了。一些 Lisps 甚至有称为属性列表的东西,可以将其视为对象。

在函数式风格中使用对象的真正诀窍是确保您不依赖它们的副作用,而是将它们视为不可变的。一种简单的方法是,每当您想更改属性时,只需使用新的详细信息创建一个对象并将其传递出去(这是 Clojure 和 Haskell 中经常使用的方法)。

我坚信功能方面在 JavaScript 中可能非常有用,但最终,您应该使用任何使代码更具可读性和适合您的东西。

于 2010-04-23T01:17:10.233 回答
5

您必须了解函数式编程和面向对象的编程在某种程度上是相互对立的。不可能既是纯功能的又是纯面向对象的。

函数式编程是关于无状态计算的。面向对象的编程都是关于状态转换的。(解释这个。希望不会太糟糕)

JavaScript 比函数式更面向对象。这意味着如果你想以纯函数式编程,你必须放弃大部分语言。特别是所有面向对象的部分。

如果您愿意对此更加务实,那么您可以使用来自纯函数世界的一些灵感。

我尝试遵守以下规则:

执行计算的函数不应该改变状态。改变状态的函数不应该执行计算。此外,改变状态的函数应该尽可能少地改变状态。目标是拥有许多只做一件事的小功能。然后,如果你需要做任何大事,你可以编写一堆小函数来做你需要的事情。

遵循这些规则可以获得许多好处:

  1. 易于重复使用。一个函数越长越复杂,它也就越专业,因此它被重用的可能性就越小。相反的含义是较短的函数往往更通用,因此更容易重用。

  2. 代码的可靠性。如果代码不那么复杂,则更容易推断代码的正确性。

  3. 当它们只做一件事时,测试功能会更容易。这样,需要测试的特殊情况就更少了。

更新:

合并了评论中的建议。

更新 2:

添加了一些有用的链接。

于 2010-04-22T10:02:17.867 回答
2

我认为,http://documentcloud.github.com/underscore/应该非常适合您的需求 - 它为函数式编程提供了最重要的高阶函数,并且没有您不具备的用于 DOM 操作的客户端函数不需要服务器端。虽然我没有这方面的经验。

附带说明:恕我直言,函数式编程的主要特征是函数的引用透明性-函数结果仅取决于其参数-函数不依赖于其他对象的更改,并且除了其结果值之外不引入任何更改。它使推理程序的正确性变得容易,并且对于实现可预测的多线程(如果相关)非常有价值。尽管 JavaScript 不是 FP 的首选语言 - 我希望不可变数据结构在性能方面的使用成本非常高。

于 2010-04-21T21:44:06.717 回答
0

所以要指出两件事,

  1. 在您的第一个示例中,您的变量不会泄漏到全局区域,并且应该这样做,尽量不要在未声明变量的情况下使用变量,即 test = 'data' 会导致数据泄漏到全局区域。

  2. 您的第二个示例也是正确的, bar1 和 bar2 只会在 Foo 对象上声明。

要记住的事情尽量不要过度使用原型设计,因为它适用于您创建的每个对象,这可能会占用大量内存,具体取决于对象的复杂程度。

如果您正在寻找应用程序开发框架,请查看ExtJs。我个人认为它非常适合您尝试开发的模型。在投入巨资之前,请记住他们的许可模式是如何运作的。

于 2010-04-20T19:12:54.273 回答