web-dev-qa-db-ja.com

Immutable.jsの関係

ジョンにアリスとボブの2人の子供がいて、ボブに猫のオリオンがいる状況を想像してみてください。

var Immutable = require('immutable');

var parent = Immutable.Map({name: 'John'});
var childrens = Immutable.List([
    Immutable.Map({name: 'Alice', parent: parent}),
    Immutable.Map({name: 'Bob', parent: parent})
]);
var cat = Immutable.Map({name: 'Orion', owner: childrens.get(1)});

数年後、ジョンはジェーンに名前を変更したいと考えています。

var renamedParent = parent.set('name', 'Jane');

...そして子供たちにそれについて知らせてください。

childrens = childrens.map(function(children) {
    children.set('parent', renamedParent);
});

ボブが変わったので、猫を更新する必要があります。

cat = cat.set('owner', childrens.get(1));

1つのオブジェクトが変更されたときに、関連するすべてのオブジェクトを自動的に更新することはできますか? カーソル を見ましたが、それが解決策かどうかはわかりません。可能であれば、例を挙げていただけますか?

26
user1518183

質問を言い換えると:

1つのオブジェクトが不変のコレクションで変更されたときに、関連するすべてのオブジェクトを自動的に更新することは可能ですか?

短い答え

番号。

長い答え

いいえ。ただし、不変のデータ構造では何も変更されないため、問題はありません。

さらに長い答え

もっと複雑です...

不変性

不変オブジェクトの要点は、不変オブジェクトへの参照がある場合、そのプロパティのいずれかが変更されているかどうかをわざわざチェックする必要がないということです。それで、それは問題ではありませんか?上手...

結果

それにはいくつかの結果があります-それらが良いか悪いかはあなたの期待に依存します:

  • 値渡し参照渡し セマンティクスに違いはありません
  • 一部の比較は簡単です
  • オブジェクトへの参照をどこかに渡すと、コードの他の部分がオブジェクトを変更することを心配する必要はありません。
  • どこかからオブジェクトへの参照を取得すると、オブジェクトが変更されることはありません。
  • 時間の変化の概念がないため、同時実行に関するいくつかの問題を回避できます
  • 何も変更されない場合、変更がアトミックであるかどうかを心配する必要はありません。
  • ソフトウェアトランザクショナルメモリ (STM)を不変のデータ構造で実装する方が簡単です

しかし、世界は可変です

もちろん実際には、時間とともに変化する値を扱うことがよくあります。不変の状態は可変の世界を説明できないように見えるかもしれませんが、人々がそれに対処するいくつかの方法があります。

このように見てください。あるIDに住所があり、別の住所に移動した場合、その住所に住んでいることはもはや真実ではないため、そのIDは新しい真のデータと一致するように変更する必要があります。ただし、住所が含まれているものを購入してから住所を変更したときに請求書を受け取った場合、請求書が作成されたときにその住所に住んでいたことは事実であるため、請求書は同じままです。その例の請求書のように、現実世界の一部のデータ表現は不変であり、IDのように変更可能なものもあります。

ここで例をとると、不変の構造を使用してデータをモデル化することを選択した場合、私の例の請求書と同じように考える必要があります。データが最新ではないことは事実かもしれませんが、ある時点で常に一貫性があり、真実であり、変更されることはありません。

変化への対処方法

では、不変のデータを使用して変更をモデル化する方法は? Clojure using VarsRefsAtoms 、および Agents 、および ClojureScript (コンパイラー)で解決された素晴らしい方法があります。 JavaScriptをターゲットとするClojureの場合)はそれらの一部をサポートします(特にAtomはClojureと同様に機能するはずですが、Ref、STM、Var、またはAgentはありません- 同時実行機能に関するClojureScriptとClojureの違いは何ですか ) 。

ClojureScriptでAtomがどのように実装されているかを見る 通常のJavaScriptオブジェクトを使用して同じことを実現できるようです。それ自体が可変であるJavaScriptオブジェクトを持つなどの場合に機能しますが、不変オブジェクトへの参照であるプロパティがあります-その不変オブジェクトのプロパティを変更することはできませんが、構築することはできます別の不変オブジェクトを作成し、最上位の変更可能オブジェクトで古いオブジェクトを新しいオブジェクトに交換します。

Haskell のような 純粋に機能的な のような他の言語は、 モナド (説明が難しいことで有名な概念-Douglas Crockford、 JavaScript:Good Parts および JSONの発見者 彼の講演で「モナドの呪い」に起因すると考えています モナドとゴナド )。

あなたの質問は単純に見えますが、それが触れる問題は実際にはかなり複雑です。もちろん、1つのオブジェクトが変更されたときにすべての関連オブジェクトを自動的に更新できるかどうかという質問に「いいえ」と答えるだけでは意味がありませんが、それよりも複雑で、不変オブジェクトでは何も変更されないと言います(したがって、この問題は発生しません)も同様に役に立ちません。

可能な解決策

常にすべての構造にアクセスするトップレベルのオブジェクトまたは変数を持つことができます。あなたが持っているとしましょう:

var data = { value: Immutable.Map({...}) }

常にdata.value(またはより適切な名前)を使用してデータにアクセスする場合は、dataをコードの他の部分に渡すことができ、状態が変化するたびに新しいImmutableを割り当てることができます。 .Map({...})をdata.valueにマップすると、その時点でdataを使用するすべてのコードが新しい値を取得します。

data.valueを新しい不変構造に更新する方法とタイミングは、状態の更新に使用するセッター関数から自動的にトリガーされるようにすることで解決できます。

別の方法は、すべての構造のレベルで同様のトリックを使用することです。たとえば、変数の元のスペルを使用します。

var parent = {data: Immutable.Map({name: 'John'}) };
var childrens = {data: Immutable.List([
    Immutable.Map({name: 'Alice', parent: parent}),
    Immutable.Map({name: 'Bob', parent: parent})
])};

ただし、値として持っているのは不変の構造ではなく、追加レベルの間接参照を導入する不変の構造への参照を持つ追加のオブジェクトであることを覚えておく必要があります。

いくつかの読書

私が提案するのは、それらのプロジェクトと記事のいくつかを調べることです。

  • 不変React PeterHauselによる記事
  • Morearty.js - React Omと同様ですが、純粋なJavaScriptで記述された不変の状態を使用します
  • react-cursor -Facebookで使用するための機能状態管理の抽象化
  • 全知 -不変データを含む高速トップダウンレンダリングを可能にするReactコンポーネントの抽象化を提供するライブラリ* Om -ReactへのClojureScriptインターフェース
  • mori -JavaScriptでClojureScriptの永続データ構造を使用するためのライブラリ
  • Fluxy -FacebookのFluxアーキテクチャの実装
  • Facebookの不変 -JavaScriptの永続的なデータコレクション Facebook React および FacebookFlux と組み合わせると、同様の問題に直面します(組み合わせる方法を探していますReactとFluxはあなたにいくつかの良いアイデアを与えるかもしれません)で不変

不変の構造を持つ可変の世界を記述することは非常に興味深く重要な問題であるため、単純な解決策を提供しなくても、この答えが役立つことを願っています。

その他のアプローチ

必ずしも一定ではないデータのプロキシである不変オブジェクトを使用した不変性への別のアプローチについては、YegorBugayenkoと彼の記事による 不変オブジェクトと常識 ウェビナーを参照してください。

Yegor Bugayenkoは、関数型プログラミングのコンテキストで通常意味するものとは少し異なる意味で「不変」という用語を使用します。彼は、不変または永続的なデータ構造と関数型プログラミングを使用する代わりに、オブジェクト指向プログラミングを本来の意味で使用することを提唱しています。実際にはオブジェクトを変更することはありませんが、状態を変更したり、変更したりするように依頼することはできます。データ、それ自体はオブジェクトから分離されていると見なされます。リレーショナルデータベースと通信する不変のオブジェクトを想像するのは簡単です。オブジェクト自体は不変である可能性がありますが、データベースに格納されているデータを更新することはできます。 RAMに格納されている一部のデータは、データベース内のデータと同じようにオブジェクトから分離されていると考えるのは少し難しいですが、実際にはそれほど違いはありません。オブジェクトを次のように考えると、いくつかの動作を公開し、それらの抽象化境界を尊重する自律エンティティは、オブジェクトを単なるデータとは異なるものとして考えることは実際には非常に理にかなっています。そうすれば、不変のオブジェクトと可変のデータを持つことができます。

何か説明が必要な場合はコメントしてください。

53
rsp

Rspが明らかにしたように、この問題は不変性によって提供される固有のトレードオフを反映しています。

これが react/flush-ish appの例 この問題に対処する1つの方法を示しています。ここで重要なのは、Javascriptの参照ではなく、数値IDが参照に使用されることです。

4
alden

私の理解では、不変オブジェクトを少し高レベルで使用している可能性があります。私の知る限り、不変オブジェクトは時間キャプチャされた状態を表すのに役立ちます。したがって、state1 === state2を比較するのは簡単です。ただし、あなたの例では、州内で時間キャプチャされた関係もあります。完全な状態を別の完全な状態と比較するのは問題ありません。データは変更されないため、非常に読みやすくなりますが、更新もほとんどできません。この問題を修正するために別のライブラリを追加する前に、時間キャプチャされた状態を実際に比較する必要がある、より低いレベルでImmutableを実装できるかどうかを確認することをお勧めします。たとえば、1つのエンティティ(人/猫)が変更されたかどうかを知るため。

したがって、不変オブジェクトを使用したい場合は、それらを使用してください。ただし、動的オブジェクトが必要な場合は、不変オブジェクトを使用しないでください。

3
Rygu

イライラしてまったく同じ問題でどこにも行かなくなった後、私は関係をオブジェクトから完全に移動し、さまざまな一般的な関連付け(1対1、1対多、多対多)のロジックを管理することにしました。別々に。

基本的に、私は双方向マップを使用して関係を管理し、関係ロジックを、従来のようにモデル化された各データではなく、オブジェクトに集中させています。つまり、たとえば、親が変更されたときを監視し、その変更を子の関係(したがってバイマップ)に伝播するためのロジックを考え出す必要がありました。ただし、独自のモジュールで抽象化されているため、将来の関係をモデル化するために再利用できます。

これを処理するためにImmutableJSを使用せず、代わりに独自のライブラリ( https://github.com/jameslk/relatedjs )をローリングしてES6機能を使用することにしましたが、同様の方法で実行できると思います。仕方。

2
jameslk

この状況は、親レコード自体ではなく、idsを使用して親を参照することで解決できる可能性があります。ここでのガイダンスとしてSQL(sorta)を探しています。レコードはメモリ内の別のレコードへのポインタを持っているべきではなく、他のレコードのidを知っている必要があります。

私のアプリでは、各オブジェクトがその参照オブジェクトの主キーのみを知っているのが好きなので、オブジェクトが変更された場合などの質問について心配する必要はありません。そのキーが同じままである限り(そうあるべきです)、私はいつでもそれを見つけることができます:

var Immutable = require('immutable');

var people = Immutable.OrderedMap({
    0: Immutable.Map({name: 'John', id: '0'}),
    1: Immutable.Map({name: 'Alice', id: '1', parentId: '0'}),
    2: Immutable.Map({name: 'Bob', id: '2', parentId: '0'})
});
var cat = Immutable.Map({name: 'Orion', ownerId: '2'});

これで、追加の更新を行うことなく、標準のimmutable.jsツールを使用して、任意のレコードの任意の特性(もちろんidを除く)を変更できます。

people = people.set('0', people.get('0').set('name', 'Jane'))
2