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 に比べて優れています。クロージャーとファーストクラスの関数があります。それらを使用して、部分適用(カリー化)など、他の関数型プログラミング手法を作成できます。

例を見てみましょう ( sys.putsnode.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);
})));

ご覧のとおり、関数型言語でオブジェクトを使用することは問題ありません。一部の Lisp には、オブジェクトと見なすことができるプロパティ リストと呼ばれるものさえあります。

関数型スタイルでオブジェクトを使用するための本当の秘訣は、オブジェクトの副作用に頼るのではなく、オブジェクトを不変として扱うことです。簡単な方法は、プロパティを変更したいときはいつでも、新しい詳細で新しいオブジェクトを作成し、代わりにそれを渡すことです (これは、Clojure と Haskell でよく使用されるアプローチです)。

JavaScript では機能的な側面が非常に役立つ可能性があると強く信じていますが、最終的には、コードをより読みやすくし、自分に適したものを使用する必要があります。

于 2010-04-23T01:17:10.233 に答える
5

関数型プログラミングとオブジェクト指向プログラミングは、互いに正反対であることを理解する必要があります。純粋な機能と純粋なオブジェクト指向の両方を実現することはできません。

関数型プログラミングは、ステートレスな計算がすべてです。オブジェクト指向プログラミングは、状態遷移がすべてです。(これをパラフレーズします。うまくいけば、それほど悪くはありません)

JavaScript は機能的というよりもオブジェクト指向です。つまり、純粋に関数型のスタイルでプログラミングしたい場合は、言語の大部分を放棄する必要があります。具体的には、オブジェクト指向のすべての部分です。

より実用的になりたい場合は、純粋に機能的な世界からインスピレーションを得て使用できるものがあります。

私は次のルールを守ろうとします:

計算を実行する関数は、状態を変更してはなりません。また、状態を変更する関数は計算を実行しないでください。また、状態を変更する関数は、状態をできるだけ変更しないようにする必要があります。目標は、1 つのことだけを行う小さな関数を多数用意することです。次に、何か大きなことをする必要がある場合は、必要なことを行うための小さな関数の束を作成します。

これらのルールに従うことで得られる多くの利点があります。

  1. 再利用の容易さ。関数が長くて複雑であるほど、その関数はより特殊化されているため、再利用できる可能性は低くなります。逆に言えば、関数が短いほど一般的になり、再利用しやすくなります。

  2. コードの信頼性。複雑さが少ないほど、コードの正確性を判断するのは簡単です。

  3. 関数が 1 つのことだけを行うと、関数のテストが容易になります。そうすれば、テストする特殊なケースが少なくなります。

アップデート:

コメントからの提案を組み込みました。

更新 2:

便利なリンクを追加しました。

于 2010-04-22T10:02:17.867 に答える
2

http://documentcloud.github.com/underscore/は必要なものにぴったりだと思います-関数型プログラミングに最も重要な高階関数を提供し、DOM 操作用のクライアント側関数はありません。サーバー側は必要ありません。私はそれを経験していませんが。

補足として、関数型プログラミングの主な機能は、関数の参照透過性です-関数の結果はそのパラメーターのみに依存します-関数は他のオブジェクトの変更に依存せず、その結果の値以外の変更は導入されません。これにより、プログラムの正確性について簡単に推論でき、予測可能なマルチスレッドの実装に非常に役立ちます (関連する場合)。JavaScript は FP に適した言語ではありませんが、不変のデータ構造を使用すると、パフォーマンスの面で非常にコストがかかると思います。

于 2010-04-21T21:44:06.717 に答える
0

したがって、2つの点を指摘します。

  1. 最初の例では、変数がグローバルエリアにリークすることはなく、その方法です。変数を宣言せずに変数を使用しないようにしてください。つまり、test='data'によってデータがグローバルエリアにリークします。

  2. 2番目の例も正しく、bar1とbar2はFooオブジェクトでのみ宣言されます。

プロトタイピングは作成するすべてのオブジェクトに適用されるため、プロトタイピングを使いすぎないように注意してください。オブジェクトの複雑さによっては、これは非常にメモリを消費する可能性があります。

アプリ開発フレームワークをお探しの場合は、ExtJsをご覧ください。個人的には、開発しようとしているモデルに完全に適合すると思います。それに多額の投資をする前に、彼らのライセンスモデルがどのように機能するかを覚えておいてください。

于 2010-04-20T19:12:54.273 に答える