JavaScriptで不変性がそれほど重要(または必要)なのはなぜですか?


206

私は現在、React JSReact Nativeフレームワークに取り組んでいます。途中、FacebookのFluxとReduxの実装について読んでいたときに、ImmutabilityまたはImmutable-JSライブラリに遭遇しました。

問題は、なぜ不変性がそれほど重要なのかということです。オブジェクトの変更の何が問題になっていますか?それは物事を単純にしませんか?

例を挙げて、オープニング画面がニュースの見出しのリストビューである単純なニュースリーダーアプリを考えてみましょう。

最初に値を持つオブジェクトの配列と言うと、最初は操作できません。それは不変性の原則が言っていることですよね?(間違っている場合は修正してください。)しかし、更新する必要がある新しいNewsオブジェクトがある場合はどうなりますか?通常の場合は、オブジェクトを配列に追加するだけで済みます。この場合、どうすれば達成できますか?ストアを削除して再作成しますか?オブジェクトを配列に追加する方が安価な操作ではありませんか?



2
不変のデータ構造と純粋な関数は、参照の透過性につながり、プログラムの動作を推論するのを非常に簡単にします。また、関数型データ構造を使用すると、無料でバックトラックを取得できます。
WorBlux 2015

Reduxの視点@bozzmobを提供しました。
prosti 2017

1
JSが何らかの関係があると考えようとするのではなく、機能パラダイムの概念として一般的にイミュラビリティについて学ぶことは有用かもしれません。Reactは関数型プログラミングのファンによって書かれています。あなたは彼らが彼らを理解するために何を知っているかを知らなければなりません。
ガーマン

これは必須ではありませんが、いくつかの良いトレードオフを提供します。可動部分はハードウェアにあるので、可変状態はソフトウェアにあります
クリスチャンデュポン

回答:


196

私は最近同じトピックについて研究しています。私はあなたの質問に答えて、これまでに学んだことを共有するように最善を尽くします。

問題は、なぜ不変性がそれほど重要なのかということです。オブジェクトの変更の何が問題になっていますか?それは物事を単純にしませんか?

基本的に、不変性は予測可能性、パフォーマンスを(間接的に)向上させ、変異追跡を可能にするという事実に帰着します。

予測可能性

突然変異は変化を隠し、(予期しない)副作用を引き起こし、厄介なバグを引き起こす可能性があります。不変性を適用すると、アプリケーションアーキテクチャとメンタルモデルをシンプルに保つことができるため、アプリケーションについての推論が容易になります。

パフォーマンス

不変オブジェクトに値を追加すると、既存の値をコピーする必要がある新しいインスタンスを作成し、メモリを消費する新しいオブジェクトに新しい値を追加する必要があることを意味しますが、不変オブジェクトは、構造共有を利用してメモリを削減できます。オーバーヘッド。

すべての更新は新しい値を返しますが、メモリの使用量(およびGCスラッシング)を大幅に削減するために内部構造が共有されます。つまり、1000要素のベクトルに追加しても、実際には1001要素の長さの新しいベクトルは作成されません。ほとんどの場合、内部では少数の小さなオブジェクトのみが割り当てられています。

詳細については、こちらをご覧ください

変異追跡

メモリ使用量の削減に加えて、不変性により、参照と値の同等性を利用してアプリケーションを最適化できます。これにより、変更があったかどうかを簡単に確認できます。たとえば、反応コンポーネントの状態変化。を使用shouldComponentUpdateして、状態オブジェクトを比較することで状態が同一かどうかを確認し、不要なレンダリングを防ぐことができます。詳細については、こちらをご覧ください

追加のリソース:

私が設定した場合、最初は値を持つオブジェクトの配列と言います。操作できません。それが不変性の原則です、そうです(私が間違っていれば訂正してください)。しかし、更新する必要がある新しいNewsオブジェクトがある場合はどうなりますか?通常の場合は、オブジェクトを配列に追加するだけで済みます。この場合、どうすれば達成できますか?ストアを削除して再作成しますか?オブジェクトを配列に追加する方が安価な操作ではありませんか?

はい、これは正しいです。これをアプリケーションに実装する方法について混乱している場合は、コアの概念を理解するためにreduxがどのようにこれを実行するかを確認することをお勧めします。

Reduxは不変性を採用しているため、例として使用するのが好きです。単一の不変の状態ツリー(と呼ばれるstore)があり、前の状態を上記のアクションと一緒に(一度に1つずつ)受け入れ、アプリケーションの次の状態を返すリデューサーによって処理されるアクションをディスパッチすることにより、すべての状態の変化が明示されます。 。中核的な原則について詳しくは、こちらをご覧ください

egghead.ioには、reduxの作者であるDan Abramovがこれらの原則を次のように説明する優れたreduxコースがあります(シナリオに合わせてコードを少し変更しました)。

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

また、これらのビデオは、以下の不変性を実現する方法をさらに詳しく示しています。


1
@naomikフィードバックをありがとう!私の意図は、概念を説明し、オブジェクトが変更されていないことを明示し、必ずしもそれを完全に実装する方法を示すことではありませんでした。ただし、私の例は少しわかりにくいかもしれません。少し更新します。
danillouz

2
@bozzmobどういたしまして!いいえ、それは正しくありません。レデューサーで不変性を強制する必要があります。つまり、ビデオで示されているような戦略に従うか、immutablejsなどのライブラリを使用できます。あなたはここここでより多くの情報を見つけることができます。
danillouz

1
@naomik ES6 constは不変性についてではありません。Mathias Bynensは、それについて素晴らしいブログ記事を書いています。
Lea Rosema、

1
@terabaudリンクを共有していただきありがとうございます。それは重要な違いだと私は同意します。^ _ ^
ありがとう

4
これについて説明してください。「変異は変更を隠します。これにより、(予期しない)副作用が発生し、厄介なバグが発生する可能性があります。不変性を強制すると、アプリケーションアーキテクチャとメンタルモデルをシンプルに保つことができ、アプリケーションの推論が容易になります。」これはJavaScriptのコンテキストではまったく当てはまらないためです。
Pavle Lekic 2017

143

不変性の逆張り的見方

TL / DR:不変性は、JavaScriptでの必要性よりファッショントレンドです。Reactを使用している場合は、状態管理での混乱を招くいくつかの設計の選択肢にきちんとした回避策を提供します。ただし、他のほとんどの状況では、それがもたらす複雑さに対して十分な価値を追加することはなく、実際のクライアントのニーズを満たすよりも、履歴書を埋め込むために役立ちます。

長い答え:以下をお読みください。

JavaScriptで不変性がそれほど重要(または必要)なのはなぜですか?

さて、あなたが尋ねてうれしいです!

先日Dan Abramovと呼ばれる非常に才能のある男が、純粋な関数と不変性を使用するReduxと呼ばれるjavascript状態管理ライブラリを書きました。彼はまた、アイデアを非常に理解しやすく(そして販売しやすく)するいくつかの本当にクールなビデオを作りました。

タイミングは完璧だった。Angularの目新しさが薄れ、JavaScriptの世界は適切なクールさを備えた最新のものにこだわる準備ができていました。このライブラリは革新的であるだけでなく、別のシリコンバレー発電所によって売り出されていたReactと完全に組み入れられました。

悲しいことに、ファッションはJavaScriptの世界を支配しています。今、アブラモフは半として称賛されており、私たちすべての単なる人間は不変性の道に身を任せなければなりません...それが理にかなっているかどうか。

オブジェクトの変更の何が問題になっていますか?

何もない!

実際、プログラマは変異するオブジェクトが存在する限り、オブジェクトをer ...に変異させています。言い換えれば、50年以上のアプリケーション開発。

そして、なぜ物事を複雑にするのですか?オブジェクトがcatあり、オブジェクトが停止した場合cat、変更を追跡するために本当に1秒必要ですか?ほとんどの人は言うだけcat.isDead = trueでそれで終わります。

(オブジェクトの変更)は物事を単純にしませんか?

はい!..もちろんそうです!

特にJavaScriptの場合、これは実際には、他の場所(データベースなど)で維持されている状態のビューをレンダリングするのに最も役立ちます。

更新する必要がある新しいNewsオブジェクトがある場合はどうなりますか?...この場合、どうすれば達成できますか?ストアを削除して再作成しますか?オブジェクトを配列に追加する方が安価な操作ではありませんか?

まあ、従来のアプローチでNewsオブジェクトを更新すれば、そのオブジェクトのメモリ内の表現が変化します(そして、ユーザーに表示されるビューなどが変わるでしょう)...

または代わりに...

セクシーなFP / Immutabilityアプローチを試して、Newsオブジェクトへの変更をすべての履歴変更を追跡する配列に追加して、配列を反復処理し、正しい状態表現が何であるかを理解することができます(ふw !)。

私はここで何が正しいのかを学習しようとしています。教えてください:)

ファッションは行き交う。猫の皮をむく方法はたくさんあります。

絶えず変化するプログラミングパラダイムのセットの混乱に耐えなければならないのは残念です。しかし、ちょっと、クラブへようこそ!!

ここで、不変性に関して覚えておくべき重要なポイントが2つあります。これらは、初心者だけが集めることができる熱狂的な強さで投げつけられます。

1)不変性は、マルチスレッド環境での競合状態を回避するのに最適です

マルチスレッド環境(C ++、Java、C#など)は、複数のスレッドがオブジェクトを変更したい場合にオブジェクトをロックする習慣があります。これはパフォーマンスに悪影響を及ぼしますが、データ破損の代替手段よりも優れています。しかし、すべてを不変にするほどよくはありません(主はHaskellを賞賛します!)。

しかし悲しいかな!JavaScriptでは、常にシングルスレッドで動作します。Webワーカー(それぞれが別のコンテキスト内で実行されます)。したがって、実行コンテキスト内でスレッド関連の競合状態(これらすべての素敵なグローバル変数とクロージャー)を持つことはできないので、不変性を支持する主要なポイントは枠を超えています。

(そうは言っても、Webワーカーで純粋な関数を使用することに利点があります。つまり、メインスレッド上のオブジェクトをいじる必要はありません。)

2)不変性は、(何らかの形で)アプリの状態の競合状態を回避できます。

そして、これが問題の本当の核心です。ほとんどの(React)開発者は、不変性とFPが、アプリケーションの状態を予測可能にするこの魔法を何らかの形で機能させることができると伝えます。

もちろん、これはデータベースの競合状態を回避できることを意味するものではありません。そのため、すべてのブラウザーすべてのユーザーを調整する必要があります。そのためには、WebSocketsなどのバックエンドプッシュテクノロジー(アプリの実行者全員に変更をブロードキャストします。

また、JavaScriptには、アプリケーションの状態を予測可能にするために不変性が必要な固有の問題があるという意味でもありません。

このやや紛らわしい主張は、単にReactを使用するとアプリケーションの状態が競合状態なりやすくなること意味しますが、その不変性により、その痛みを取り除くことができます。どうして?Reactは特別なので、コヒーレントな状態管理が2番目に行われる高度に最適化されたレンダリングライブラリとして設計されているため、コンポーネントの状態は、制御できないイベントの非同期チェーン(別名「一方向データバインディング」)によって管理されます。以上、状態を直接変更しないことを思い出してあなたに依存してます...

このコンテキストを考えると、不変性の必要性がJavaScriptとほとんど関係がなく、Reactの競合状態と多くの関係があることが簡単にわかります。あなたの状態は現在であり、混乱することになるので、不変性を使用してすべての履歴変更を追跡することは完全に理にかなっています

3)競合状態は明らかに悪い。

まあ、Reactを使用している場合はそうかもしれません。ただし、別のフレームワークを選択した場合はまれです。

その上、通常、対処する必要があるはるかに大きな問題があります...依存関係の地獄のような問題。肥大化したコードベースのように。CSSが読み込まれないように。ビルドプロセスが遅い、または反復がほぼ不可能になるモノリシックなバックエンドに行き詰まっているようなものです。経験の浅い開発者のように、何が起こっているのかを理解せず、物事を混乱させるようなものです。

ええと。現実。しかし、ねえ、誰がそれを気にしますか?

4)不変性は参照タイプ利用して、すべての状態変化を追跡することによるパフォーマンスへの影響を軽減します。

なぜなら真剣に、状態が変化するたびにコピーするつもりなら、それが賢明であることを確認した方がいいからです。

5)不変性により、元に戻すことができます

なぜなら、これは、プロジェクトマネージャーが求める最大の機能だからです。

6)不変状態はWebSocketsと組み合わせて多くの素晴らしい可能性を秘めています

最後に重要なことですが、状態デルタの蓄積は、WebSocketsと組み合わせてかなり説得力のある事例を作り出します。これにより、不変のイベントのフローとして状態を簡単に消費できます ...

ペニーがこの概念に落ちたら(状態はイベントの流れであり、最新のビューを表す大まかなレコードのセットではなく)、不変の世界が住む魔法の場所になります。時間そのもの超越するイベントが生み出す驚異と可能性の土地。完了したら、右、これは間違いなくリアルタイムでEASIアプリ作ることができるとER達成するために、あなたはちょうど彼らがすることができますので、興味のあるすべての人へのイベントの流れ放送、独自の表現を構築する現在のを、共同流れの中に、自分の変更を書き戻しを。

しかし、ある時点で目を覚ますと、そのすべての不思議と魔法が無料で提供されないことに気づきます。熱心な同僚とは異なり、利害関係者(そう、あなたに支払う人)は、哲学やファッションにはほとんど関心がなく、販売できる製品を構築するために支払うお金に多くの関心を持っています。そして、一番下の行は、不変のコードを書くのがより難しく、それを壊すのがより簡単であることに加えて、それをサポートするバックエンドがなければ、不変のフロントエンドを持っている意味がほとんどありません。ときに(と!あれば)、あなたは最終的にあなたが公開する必要があることをあなたの利害関係者を説得し、経由でイベントを消費プッシュtechology WebSocketをのような、あなたは何を見つけるの痛みは、それが生産に拡大することです


ここで、いくつかのアドバイスとして、それを受け入れることを選択する必要があります。

FP / Immutabilityを使用してJavaScriptを作成する選択肢は、アプリケーションのコードベースをより大きく、より複雑にし、管理を困難にする選択肢でもあります。あなたが何をしているのかわからない限り、私はこのアプローチをReduxのレデューサーに限定することを強く主張します...そして、先に進んで不変を使用する場合は、不変の状態をアプリケーションスタック全体に適用するだけでなく、そうでなければクライアントの真の価値を見逃しているので、クライアント側。

さて、あなたがあなたの仕事で選択をすることができるほど幸運であるならば、あなたの知恵を試してみて(またはそうではない)、あなたに支払っている人が正しいことをしてください。これは、経験、内臓、または周囲の状況に基づいて行うことができます(確かに、全員がReact / Reduxを使用している場合は、作業を続行するためのリソースを見つける方が簡単であるという有効な議論があります)。Resume Driven DevelopmentまたはHype Driven Developmentのいずれかのアプローチを試すことができます。彼らはもっとあなたのようなものかもしれません。

要するに、不変性について言われるべきことは、少なくとも次の流行が来るまで、あなたが仲間とファッショナブルになるということです。


このセルフセラピーのセッションの後で、これをブログの記事として追加したことを指摘したいと思います => JavaScriptの不変性:逆張りのビュー。あなたが胸から降りたいという強い気持ちを持っているなら、そこに気軽に返信してください;)


10
こんにちは、スティーブン、はい。immutable.jsとreduxを検討したとき、これらすべての疑問がありました。しかし、あなたの答えは素晴らしいです!それは多くの価値を追加し、私が疑問に思っていたすべてのポイントに対処してくれてありがとう。不変のオブジェクトで何ヶ月も働いた後でも、今ははるかに明確/優れています。
bozzmob 2017

5
私は2年以上Flux / ReduxでReactを使用していますが、これ以上あなたに同意することはできませんでした。
Pavle Lekic 2017

6
不変性の見解がチームとコードベースのサイズにかなりうまく相関していることを強く疑っています。主な支持者がシリコンバレーの巨人であるのは偶然ではないと思います。そうは言っても、私は敬意を払わずに同意します。gotoを使用しないのは有用な分野のように、不変性は有用な分野です。または単体テスト。またはTDD。または静的型分析。常にそうしているわけではありません。また、実用性は誇大広告に直角であるとも言えます。有用/余分でセクシー/退屈なマトリックスには、それぞれの例がたくさんあります。"hyped" !== "bad"
Jared Smith

4
こんにちは@ftor、良い点、物事を他の方向に行きすぎている。しかし、「JavaScriptでの不変性」の記事や議論が非常に多いため、バランスを取る必要があると感じました。したがって、初心者は反対の視点を持ち、どちらの方法でも判断を下すことができます。
Steven de Salas

4
有益で、見事にタイトルが付けられています。この答えが見つかるまで、同じような見方をしているのは私だけだと思いました。私は不変性の価値を認識していますが、気になるのは、それが他のすべての技術を圧迫する 教義になっていることです(たとえば、KnockoutJSに実装されている入力フォーマットにめちゃくちゃ便利な2ウェイバインディングを損なうため)。
Tyblitz

53

問題は、なぜ不変性がそれほど重要なのかということです。オブジェクトの変更の何が問題になっていますか?それは物事を単純にしませんか?

実際には、その逆です。可変性は、少なくとも長期的には物事をより複雑にします。はい、好きな場所で変更できるため、最初のコーディングが簡単になりますが、プログラムが大きくなると問題になります。値が変更された場合、何が変更されましたか?

すべてを不変にすると、それ以上データを変更することができなくなります。関数に値を渡した場合、その関数では値を変更できないことは確かです。

簡単に言えば、不変の値を使用すると、コードを推論するのが非常に簡単になります。誰もがデータの一意の*コピーを取得するため、データを混乱させたり、コードの他の部分を壊したりすることはできません。これにより、マルチスレッド環境での作業がどれほど簡単になるか想像してみてください。

注1:実行していることによっては、不変性のパフォーマンスが低下する可能性がありますが、Immutable.jsなどは可能な限り最適化されます。

注2:万が一の場合、確信が持てませんでした。Immutable.jsとES6のconst意味は大きく異なります。

通常の場合は、オブジェクトを配列に追加するだけで済みます。この場合、どうすれば達成できますか?ストアを削除して再作成しますか?オブジェクトを配列に追加する方が安価な操作ではありませんか?PS:この例が不変性を説明する正しい方法ではない場合は、正しい実際的な例を教えてください。

はい、あなたのニュースの例は完全に良いです、そしてあなたの推論は正確です:あなたはあなたの既存のリストを単に修正することができないので、あなたは新しいものを作成する必要があります:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

1
私はこの回答に同意しませんが、質問の「実践的な例から学びたい」という部分については触れていません。複数の領域で使用されているニュースヘッダーのリストへの単一の参照は良いことだと主張することができます。「リストを更新する必要があるのは1回だけで、ニュースリストを参照するものはすべて無料で更新されます」-彼が提示したような一般的な問題を解決し、不変性を使用する貴重な代替案を示す方が良いと思います。
ありがとう

1
答えが役に立てて嬉しいです!あなたの新しい質問に関して:システムを推測しないでください:)この正確なケースでは、「構造共有」と呼ばれるものがGCスラッシングを劇的に削減します。リストに10,000項目があり、さらに10項目を追加した場合、不変だと思います。 jsは、以前の構造を可能な限り再利用しようとします。Immutable.jsがメモリについて心配するようにしましょう。可能性が高まる可能性があります。
TwoStraws、2015

6
Imagine how much easier this makes working in a multi-threaded environment!->他の言語についてはOKですが、これはシングルスレッドJavaScriptの利点ではありません。
Steven de Salas 2017年

1
@StevendeSalasは、JavaScriptは主に非同期でイベント駆動型であることに注意してください。それはレース条件に全く影響されません。
Jared Smith

1
@JaredSmithはまだ私のポイントが残っています。FPと不変性は、マルチスレッド環境ではデータの破損やリソースロックを回避するための強力なパラダイムですが、JavaScriptではシングルスレッドであるためそうではありません。神聖な知識の欠落がない限り、ここでの主なトレードオフは、競合状態を回避するために、コードをより複雑(かつ低速)にする準備ができているかどうかです...これは、ほとんどの人よりはるかに問題が少ないです。思う。
Steven de Salas 2017

37

他の回答も問題ありませんが、実用的なユースケースについての質問に(他の回答のコメントから)対処するには、実行中のコードから少し離れて、鼻のすぐ下にあるユビキタスな回答を確認します:git。コミットをプッシュするたびにリポジトリのデータを上書きするとどうなりますか?

さて、不変のコレクションが直面する問題の1つであるメモリの肥大に直面しています。Gitは、変更を加えるたびにファイルの新しいコピーを作成するだけでなく、差分を追跡するだけの十分な機能を備えています。

私はgitの内部の仕組みについてはあまり詳しくありませんが、参照するライブラリと同様の戦略、つまり構造共有を使用しているとしか想定できません。内部的には、ライブラリはtryまたは他のツリーを使用して、異なるノードのみを追跡します。

対数時間で動作するよく知られたツリー演算アルゴリズムがあるため、この戦略はインメモリデータ構造に対しても適度に機能します。

別の使用例:ウェブアプリに[元に戻す]ボタンが必要だとします。データの不変表現を使用すると、そのような実装は比較的簡単です。ただし、ミューテーションに依存している場合は、世界の状態をキャッシュしてアトミックな更新を行うことを心配する必要があります。

要するに、実行時のパフォーマンスと学習曲線の不変性の代償を払う必要があります。しかし、経験豊富なプログラマーなら誰でも、デバッグ時間はコードの作成時間よりも桁違いに多いと言うでしょう。また、ランタイムパフォーマンスへのわずかな影響は、ユーザーが耐える必要のない状態関連のバグよりも重要です。


1
私が言う素晴らしい例。不変性についての私の理解はより明確になりました。Jaredに感謝します。実際には、実装の1つは[元に戻す]ボタン:Dです。
bozzmob

3
gitでパターンが意味を成しているからといって、どこでも同じことが意味をなすわけではありません。gitでは、実際に保存されているすべての履歴に関心があり、さまざまなブランチをマージできるようにしたいと考えています。フロントエンドでは、州の歴史の大部分を気にする必要はなく、このような複雑さはすべて必要ありません。
スキー

2
@Skiデフォルトではないため、複雑です。私は通常、自分のプロジェクトでmoriやimmutable.jsを使用しません。サードパーティの担当者を引き受けるのは常にためらっています。しかし、それがデフォルト(clojurescriptの1つ)であるか、少なくともオプトインのネイティブオプションがあった場合、私は常にそれを使用します。
Jared Smith

ジョーアームストロングは、パフォーマンスについて心配しないでください。数年待つだけで、ムーアの法則によって処理されます。
ximo

1
@JaredSmithそうですね、物事はますます小さくなり、より多くのリソースが制約されています。それがJavaScriptの制限要因になるかどうかはわかりません。パフォーマンスを改善するための新しい方法を探し続けています(Svelteなど)。ちなみに、私はあなたの他のコメントに完全に同意します。不変のデータ構造を使用することの複雑さや難しさは、多くの場合、概念のサポートが組み込まれていない言語に起因します。Clojureは、言語に組み込まれているため不変性を単純化し、言語全体がアイデアに基づいて設計されました。
ximo

8

問題は、なぜ不変性がそれほど重要なのかということです。オブジェクトの変更の何が問題になっていますか?それは物事を単純にしませんか?

可変性について

技術的な観点から見ると、変異性に問題はありません。高速で、メモリを再利用しています。開発者は最初からそれに慣れています(私が覚えているように)。可変性の使用と、この使用がもたらす可能性のある問題に問題があります。

オブジェクトが何かと共有されていない場合、たとえば、関数のスコープ内に存在し、外部に公開されていない場合、不変性の利点を確認することは困難です。本当にこの場合、不変であることは意味がありません。不変性の感覚は、何かが共有されたときに始まります。

変異性頭痛

可変共有構造は、多くの落とし穴を簡単に作成する可能性があります。参照へのアクセス権を持つコードの任意の部分の変更は、この参照を表示できる他の部分に影響を与えます。そのような影響は、異なるモジュールを認識していないはずの場合でも、すべてのパーツを結合します。1つの関数の変異は、アプリのまったく異なる部分でクラッシュする可能性があります。そのようなことは悪い副作用です。

次に、突然変異に関する問題は、破損した状態です。変異手順が途中で失敗すると、破損した状態が発生する可能性があり、一部のフィールドは変更され、一部は変更されませんでした。

さらに、変異があると、変化を追跡することが困難になります。単純な参照チェックでは違いが表示されません。詳細なチェックで何を変更する必要があるかを知るためです。また、変更を監視するには、いくつかの観察可能なパターンを導入する必要があります。

最後に、突然変異は信頼不足の理由です。構造を変更できる場合に、ある構造が価値を望んでいることを確認する方法。

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

上記の例が示すように、可変構造を渡すことは常に異なる構造を持つことで終了できます。関数doSomethingは、外部から与えられた属性を変更しています。コードに対する信頼はありません。自分が何を持っているのか、何を持っているのか本当にわかりません。これらの問題はすべて次の理由で発生します。可変構造がメモリへのポインタを表している。

不変性は値に関するものです

不変性とは、変更が同じオブジェクト、構造で行われるのではなく、変更が新しいもので表現されることを意味します。これは、参照がメモリポインタだけでなく値を表すためです。すべての変更は新しい価値を生み出し、古いものには影響しません。このような明確なルールは、信頼とコードの予測可能性をもたらします。関数は、ミューテーションの代わりに、独自の値を持つ独自のバージョンを処理するため、安全に使用できます。

メモリコンテナの代わりに値を使用すると、すべてのオブジェクトが特定の変更不可能な値を表すことが確実になり、安全に使用できます。

不変の構造は値を表します。

私は中程度の記事の主題にさらに飛び込んでいます-https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310


6

JavaScriptで不変性がそれほど重要(または必要)なのはなぜですか?

不変性はさまざまなコンテキストで追跡できますが、最も重要なことは、アプリケーションの状態とアプリケーションのUIに対して追跡することです。

私はJavaScript Reduxパターンを非常にトレンディでモダンなアプローチとみなします。

UIについては、予測可能にする必要があります。次の場合は予測可能ですUI = f(application state)

(JavaScriptの)アプリケーションは、レデューサー関数を使用して実装されたアクションによって状態を変更します

レデューサー関数は単にアクションと古い状態を取り、古い状態をそのままにして新しい状態を返します。

new state  = r(current state, action)

ここに画像の説明を入力してください

利点は次のとおりです。すべての状態オブジェクトが保存されているため、状態をタイムトラベルし、アプリを任意の状態でレンダリングできます。 UI = f(state)

したがって、簡単に元に戻す/やり直すことができます。


これらすべての状態を作成していると思われる場合でも、依然としてメモリ効率が良い可能性があります。Gitとの類似性は素晴らしく、シンボリックリンク(inodeに基づく)を持つLinux OSでも同様の類似性があります。


5

JavaScriptの不変性のもう1つの利点は、一時的な結合が減少することです。これは、一般的に設計に大きな利点があります。次の2つのメソッドを持つオブジェクトのインターフェースを考えてみます。

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

それはへの呼び出しがある場合もありbaz()への呼び出しのために有効な状態にオブジェクトを取得するために必要とされてbar()正しく動作します。しかし、どうやってこれを知っていますか?

f.baz();
f.bar(); // this is ok

f.bar();
f.baz(); // this blows up

それを理解するには、パブリックインターフェイスを調べてもすぐにはわからないため、クラスの内部を精査する必要があります。この問題は、多数の変更可能な状態とクラスを持つ大規模なコードベースで爆発する可能性があります。

Fooが不変である場合、これはもはや問題ではありません。クラスの内部状態は変更できないため、呼び出し可能であるbazbar、または任意の順序で可能であると想定しても安全です。


4

むかしむかし、スレッド間のデータ同期に問題がありました。この問題は非常に苦痛で、10以上の解決策がありました。一部の人々はそれを根本的に解決しようとしました。関数型プログラミングが生まれた場所です。それはマルクス主義のようなものです。Dan AbramovがこのアイデアをJSに売り込んだ方法は理解できませんでした。シングルスレッドだからです。彼は天才です。

小さな例を挙げることができます。__attribute__((pure))gccに属性があります。コンパイラーは、特別に明確にしない限り、関数が純粋かどうかを解決しようとします。状態が変更可能であっても、関数は純粋である可能性があります。不変性は、機能が純粋であることを保証する100以上の方法の1つにすぎません。実際、関数の95%は純粋です。

実際に重大な理由がない場合は、不変性などの制限を使用しないでください。状態を「元に戻す」場合は、トランザクションを作成できます。通信を単純化したい場合は、不変データを使用してイベントを送信できます。それはあなた次第です。

私はこのメッセージをポストマルクス主義共和国から書いています。どんな考えの過激化も間違った方法だと私は確信しています。


3番目の段落はとても理にかなっています。有難うございます。'ある状態を「元に戻す」場合は、トランザクションを作成できます' !!
bozzmob

ちなみに、マルクス主義との比較はOOPでも行うことができます。Javaを覚えていますか?JavaScriptでのJavaの奇妙な部分とは?誇大広告は決して良いことではなく、過激化と二極化を引き起こします。歴史的に、OOPはFacebookによるReduxの宣伝よりもはるかに宣伝されていました。彼らは確かに最善を尽くしましたが。
ximo

4

別のテイク...

私のもう1つの回答は、非常に実用的な観点からの質問に対応していますが、それでも私は好きです。これは、質問の答えとなる可能性がありますが、私の既存の答えにはあまり合わない、退屈な哲学的暴言であるため、これを補遺ではなく別の答えとして追加することにしました。

TL; DR

小規模なプロジェクトでも不変性は役立つ場合がありますが、存在するのでそれがあなたのためのものであることを前提にしないでください。

はるかに長い答え

注:この回答の目的で、私は「規律」という言葉を使用して、いくつかの利益のための自己否定を意味しています。

これは別の質問に似ています:「Typescriptを使用すべきですか?なぜJavaScriptでタイプがそれほど重要なのですか?」。それも同様の答えを持っています。次のシナリオを検討してください。

あなたは、約5000行のJavaScript / CSS / HTMLコードベースの唯一の作成者および保守者です。あなたの準技術上司は、Typescript-as-the-new-hotnessについて何かを読んで、そこに移動したいと思うかもしれないが、決定はあなたに任せることを提案します。だから、あなたはそれについて読んだり、それをいじったりします。

さて、あなたは選択の余地があります。Typescriptに移行しますか?

Typescriptにはいくつかの説得力のある利点があります。インテリセンス、エラーを早期にキャッチする、APIを事前に指定する、リファクタリング時に問題を修正するのが簡単、テストが少ない。Typescriptにもいくつかのコストがあります。特定の非常に自然で正しいJavaScriptイディオムは、特に強力な型システムではモデル化が難しい場合があります。注釈は、LoCを増加させ、既存のコードベースを書き換える時間と労力、ビルドパイプラインの追加ステップなどを行います。より基本的には、コードが正しい可能性が高いという約束と引き換えに、可能な正しいJavaScriptプログラムのサブセットを切り出します。これは任意に制限されます。それが全体のポイントです。あなたはあなたを制限する何らかの規律を課します(うまくいけば、足で自分を撃つことから)。

質問に戻り、上の段落の文脈で言い換えると、それだけの価値があるのでしょうか。

ここで説明するシナリオでは、中小規模のJSコードベースに精通している場合は、Typescriptを使用するほうが実用的というよりも美的だと思います。そして、それは結構です、美学に問題は何もありません、それらは必ずしも魅力的ではありません。

シナリオB:

あなたは転職してFoo Corpの基幹業務プログラマーになりました。90000LoC(およびカウント)のJavaScript / HTML / CSSコードベースで10人のチームと協力し、バベル、webpackを含むかなり複雑なビルドパイプラインを使用しています。 、一連のポリフィル、さまざまなプラグイン、状態管理システム、〜20のサードパーティライブラリ、〜10の内部ライブラリ、社内スタイルガイドのルールを備えたリンターのようなエディタープラグインなどと反応します。

あなたが5k LoCの男/女だった頃は、それほど重要ではありませんでした。ドキュメントでさえ大したことではなく 6か月後にコードの特定の部分に戻ったとしても、簡単に理解できました。しかし、今、規律は素晴らしいだけでなく必要です。その分野にはTypescriptが含まれない場合がありますが、静的分析のほか、他のすべての形式のコーディング分野(ドキュメント、スタイルガイド、ビルドスクリプト、回帰テスト、CI)が含まれる可能性があります。規律はもはや贅沢ではなく、必需品です。

これらすべてがGOTO1978年に当てはまりました。Cでのちょっぴり小さなブラックジャックゲームはGOTOsとスパゲッティロジックを使用でき、それを介して自分の冒険を選択することはそれほど大きな問題ではありませんでしたが、プログラムが大きくなり、より野心的で、まあ、規律のない使用はGOTO持続できませんでした。そして、これらすべてが今日の不変性に当てはまります。

静的型のように、それを維持/拡張するエンジニアのチームで大規模なコードベースに取り組んでいない場合、不変性を使用するという選択は実用的というよりは美的です。利点はまだそこにありますが、まだコストを上回らないかもしれません。

しかし、すべての有用な分野と同様に、それはもはやオプションではなくなります。健康的な体重を維持したい場合は、アイスクリームを含む分野はオプションです。しかし、私が競技選手になりたいのであれば、アイスクリームを食べるかどうかの私の選択は、私の目標の選択に含まれています。ソフトウェアで世界を変えたいのであれば、不変性はそれ自体の重みで崩壊するのを避けるために必要なものの一部かもしれません。


1
+1気に入った。より多くの点でJared。それでも不変性は、チームをそれ自体の規律の欠如から救うことはできません。😉
スティーブンデサラス

@StevendeSalasは一種の規律です。そのため、他の形式のソフトウェアエンジニアリングの分野と相関している(ただし、それに代わるものではない)と思います。取って代わるのではなく、補完します。しかし、あなたの回答のコメントで述べたように、同じ巨大なコードベースを使いこなすエンジニアの群れを抱えるテックジャイアントによってプッシュされていることにはまったく驚いていません。私はほとんどの場合、オブジェクトを変更しませんが、どのような形式の強制も使用しません。
Jared Smith

0

私は、ライブラリ(redux、vuexなど)のようなすべての不変のストレージを置き換えることができる可変(または不変)状態のフレームワークに依存しないオープンソース(MIT)libを作成しました。

不変の状態は私にとっては醜いものでした(行うべき作業が多すぎ(単純な読み取り/書き込み操作には多くのアクションが必要))、コードは読みにくく、大きなデータセットのパフォーマンスは受け入れられません(コンポーネント全体の再レンダリング:/)。

ディープ状態オブザーバ Iは、ドット表記法および使用ワイルドカードを有する唯一のノードを更新することができます。また、変更された具体的な値のみを保持して、状態の履歴(元に戻す/やり直し/タイムトラベル)を作成することもできます。つまり、{path:value}メモリ使用量が少なくなります。

深い状態オブザーバ私は微調整をすることができ、物事や性能を大幅に向上させることができるので、私は、コンポーネントの挙動を超える粒制御しています。コードが読みやすくなり、リファクタリングがはるかに簡単になります。パス文字列を検索して置き換えるだけです(コード/ロジックを変更する必要はありません)。


-1

プロ不変オブジェクトの主な理由は、オブジェクトの状態を有効に保つことです。

と呼ばれるオブジェクトがあるとしarrます。このオブジェクトは、すべてのアイテムが同じ文字である場合に有効です。

// this function will change the letter in all the array
function fillWithZ(arr) {
    for (var i = 0; i < arr.length; ++i) {
        if (i === 4) // rare condition
            return arr; // some error here

        arr[i] = "Z";
    }

    return arr;
}

console.log(fillWithZ(["A","A","A"])) // ok, valid state
console.log(fillWithZ(["A","A","A","A","A","A"])) // bad, invalid state

場合はarr不変オブジェクトになり、その後、私たちは、ARRが有効な状態に常にあることを確認します。


arr電話をかけるたびに変異すると思いますfillWithZ
rodrigo-silveira

immutable.jsを使用すると、変更するたびにオブジェクトの新しいコピーが取得されます。したがって、元のオブジェクトは変更されません
bedorlan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.