Подтвердить что ты не робот

Связи Immutable.js

Представьте себе ситуацию, когда у Джона есть две девочки Алиса и Боб, а у Боба есть кошка Орион.

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));

Возможно ли автоматическое обновление всех связанных объектов при изменении одного объекта? Я смотрел на курсоры, но я не уверен, что они являются решением. Если возможно, можете ли вы привести мне пример?

4b9b3361

Ответ 1

Перефразируя вопрос:

Возможно ли автоматическое обновление всех связанных объектов при изменении одного объекта в неизменяемой коллекции?

Короткий ответ

Нет.

Длинный ответ

Нет, но ничего не меняется в неизменяемой структуре данных, так что это не проблема.

Более длинный ответ

Это сложнее...

Неизменность

Весь смысл неизменяемых объектов заключается в том, что если у вас есть ссылка на неизменяемый объект, вам никогда не придется проверять, изменилось ли какое-либо его свойство. Итак, это не проблема? Ну...

Последствия

Есть некоторые последствия этого: хорошие или плохие ли они зависят от ваших ожиданий:

  • Нет никакой разницы между pass-by-value и pass-by-reference семантика
  • Некоторые сравнения могут быть проще
  • Когда вы передаете ссылку на объект где-то, вам не нужно беспокоиться о том, что какая-то другая часть кода изменит его.
  • Когда вы получаете ссылку на объект откуда-то, вы знаете, что он никогда не изменится.
  • Вы избегаете некоторых проблем с concurrency, потому что нет понятия изменения времени
  • Когда ничего не меняется, вам не нужно беспокоиться, являются ли изменения атомарными
  • Проще реализовать транзакционную память программного обеспечения (STM) с неизменяемыми структурами данных

Но мир изменен

Конечно, на практике мы часто имеем дело со значениями, которые меняются во времени. Может показаться, что неизменяемое состояние не может описывать изменчивый мир, но есть некоторые способы, с которыми люди справляются с этим.

Посмотрите на это так: если у вас есть свой адрес на каком-то ID, и вы переходите на другой адрес, этот идентификатор должен быть изменен, чтобы соответствовать новым истинным данным, потому что уже не так, что вы живете по этому адресу, Но когда вы получаете счет-фактуру, когда вы покупаете что-то, что содержит ваш адрес, а затем вы меняете свой адрес, счет-фактура остается прежним, поскольку по-прежнему верно, что вы проживали по этому адресу, когда был составлен счет-фактура. Некоторые представления данных в реальном мире неизменяемы, например, счета-фактуры в этом примере, а некоторые являются изменяемыми, как идентификаторы.

Теперь, взяв ваш пример, если вы решите использовать неизменяемые структуры для моделирования своих данных, вы должны подумать об этом так, как вы думаете о счете-фактуре из моего примера. Возможно, данные не обновлены, но в какой-то момент времени они всегда будут согласованными и истинными, и они никогда не изменятся.

Как справиться с изменением

Итак, как моделировать изменение с неизменяемыми данными? Существует хороший способ, который был решен в Clojure с помощью Vars, Refs, Atoms и Agents и ClojureScript (компилятор для Clojure, который нацелен на JavaScript) поддерживает некоторые из них (в частности, Atoms должны работать как в Clojure, но нет ссылок, STM, Vars или агентов - см. каковы различия между ClojureScript и Clojure в отношении concurrency функций).

Рассматривая как Atoms реализованы в ClojureScript, кажется, что вы можете просто использовать обычные объекты JavaScript для достижения того же. Он будет работать для таких вещей, как наличие объекта JavaScript, который сам изменяет, но имеет свойство, которое является ссылкой на неизменяемый объект - вы не сможете изменить какие-либо свойства этого неизменяемого объекта, но вы сможете построить другой неизменяемый объект, и замените старый на новый в своем изменчивом объекте верхнего уровня.

Другие языки, такие как Haskell, которые чисто функциональные могут иметь разные способы борьбы с изменчивым миром, например, monads (концепция, которую трудно объяснить), Дуглас Крокфорд, автор JavaScript: Хорошие части и первооткрыватель JSON приписывает его "монадическое проклятие" в его речи Монады и Гондады).

Ваш вопрос кажется простым, но проблема, с которой он сталкивается, на самом деле довольно сложная. Разумеется, было бы не просто ответить "Нет" на ваш вопрос, можно ли автоматически обновлять все связанные объекты при изменении одного объекта, но это сложнее, чем это, и говорить, что в неизменяемых объектах ничего не меняется ( поэтому эта проблема никогда не произойдет) будет также бесполезной.

Возможные решения

У вас может быть объект или переменная верхнего уровня, из которой вы всегда получаете доступ ко всем вашим структурам. Скажем, у вас есть:

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})
])};

но тогда вы должны помнить, что то, что вы имеете в качестве значений, не является неизменяемыми структурами, а скорее дополнительными объектами со ссылками на неизменяемые структуры, которые вводят дополнительный уровень косвенности.

Некоторое чтение

Я бы предложил посмотреть некоторые из этих проектов и статей:

  • Неизменяемое действие статьи Peter Hausel
  • Morearty.js - использование неизменяемого состояния в React как в Om, но написанный в чистом JavaScript
  • react-cursor - абстракция функционального управления состоянием для использования с Facebook
  • Omniscient - библиотека, предоставляющая абстракцию для компонентов React, которая позволяет быстро рендерить сверху вниз неизменяемые данные * Om - Интерфейс ClojureScript для реагирования
  • mori - библиотека для использования постоянных структур данных ClojureScript в JavaScript
  • Fluxy - реализация архитектуры Facebook Flux
  • Facebook Immutable - постоянные коллекции данных для JavaScript, которые в сочетании с Facebook React и Facebook Flux сталкивается с аналогичными проблемами, которые у вас есть (поиск того, как объединить Immutable с React and Flux, может дать вам некоторые хорошие идеи)

Я надеюсь, что этот ответ, даже если не даст простого решения, тем не менее будет полезен, потому что описание изменчивого мира с неизменяемыми структурами - очень интересная и важная проблема.

Другие подходы

Для другого подхода к неизменяемости, с неизменяемыми объектами, которые являются прокси-данными для данных, которые необязательно являются постоянными, см. Неизменяемые объекты против общего смысла вебинар Егора Бугаенко и его статьи:

Егор Бугаенко использует термин "неизменный" в несколько ином смысле, чем то, что обычно означает в контексте функционального программирования. Вместо использования неизменных или постоянных структур данных и функционального программирования он выступает за использование объектно-ориентированного программирования в первоначальном смысле этого слова так, что вы никогда не мутируете какой-либо объект, но можете попросить его изменить какое-либо состояние или изменить некоторые данные, которые сами по себе считаются отдельными от объекта. Легко представить непреложный объект, который говорит с реляционной базой данных. Сам объект может быть неизменным, но он может обновлять данные, хранящиеся в базе данных. Несколько сложнее представить, что некоторые данные, хранящиеся в ОЗУ, можно рассматривать как одинаково отдельно от объекта как данные в базе данных, но на самом деле нет большой разницы. Если вы думаете об объектах как автономных объектах, которые выставляют какое-то поведение, и вы уважаете их границы абстракции, тогда на самом деле имеет смысл думать о объектах как нечто отличное от просто данных, а затем вы можете иметь неизменяемые объекты с изменяемыми данными.

Прокомментируйте, если что-то нуждается в уточнении.

Ответ 2

Как поясняется rsp, эта проблема отражает неотъемлемый компромисс, предлагаемый неизменностью.

Здесь пример action/flux-ish app, демонстрирующий один из способов решения этой проблемы. Ключевым моментом здесь является то, что числовые идентификаторы используются для ссылок, а не ссылок на Javascript.

Ответ 3

Из того, что я понимаю, вы можете использовать неизменяемые объекты на слишком высоком уровне. Насколько я знаю, неизменяемые объекты полезны для представления состояний, захваченных временем. Поэтому легко сравнить state1 === state2. Однако в вашем примере у вас есть также зафиксированные по времени отношения в состоянии. Что хорошо, чтобы сравнить полное состояние с другим полным состоянием, потому что это неизменные данные, поэтому они очень читаемы, но в связи с этим также вряд ли могут быть обновлены. Предлагаю прежде, чем вы добавите еще одну библиотеку, чтобы исправить эту проблему, попробуйте и посмотрите, можете ли вы реализовать Immutable на более низком уровне, где вам действительно нужно сравнивать состояния, записанные во времени. Например, чтобы узнать, изменилось ли 1 сущность (человек/кошка).

Итак, где вы хотите использовать неизменяемые объекты, используйте их. Но если вам нужны динамические объекты, не используйте неизменяемые объекты.

Ответ 4

Возможно, эта ситуация может быть решена с помощью 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'});

Теперь я могу изменить любую характеристику любой записи (кроме id, конечно), используя стандартные инструменты immutable.js, не требуя дополнительного обновления.

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

Ответ 5

После разочарования и отсутствия каких-либо проблем с этой же проблемой я решил полностью переместить отношения из своих объектов и управлять логикой различных общих ассоциаций (один к одному, один-ко-многим, много- -many) отдельно.

По сути, я управляю отношениями с двунаправленными картами и сохраняю логику отношений, централизованную в объекте, а не в каждой из моих смоделированных данных, как это традиционно. Это означает, что мне нужно было придумать логику наблюдения, например, когда родитель изменил и распространил изменения на дочерние отношения (следовательно, бимапы). Однако, поскольку он абстрагируется в своем собственном модуле, его можно повторно использовать для моделирования будущих отношений.

Я решил не пойти с ImmutableJS для обработки этого, вместо того, чтобы перекладывать свою собственную библиотеку (https://github.com/jameslk/relatedjs) и использовать функции ES6, но я полагаю это можно сделать аналогичным образом.