2015-08-20 2 views
18

У меня есть хорошее представление о преимуществах использования неизменяемых данных в моих приложениях, и я довольно доволен идеей использования этих неизменяемых структур в простой синхронной среде программирования.Неизменяемые данные в асинхронных системах

Там хороший пример где-то на переполнение стека, который описывает управление состоянием для игры, передавая состояние вместе в серии рекурсивных вызовов, что-то вроде этого:

function update(state) { 
    sleep(100) 

    return update({ 
    ticks: state.ticks + 1, 
    player: player 
    }) 
} 

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

Кажется довольно легко перевести это на простую асинхронную модель, например, на Javascript.

function update(state) { 
    const newState = { 
    player, 
    ticks: state.ticks + 1 
    }; 

    setTimeout(update.bind(this, newState), 100); 
} 

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

Если мы добавим событие click к примеру, мы получим код, который выглядит следующим образом.

window.addEventListener('click', function() { 
    // I have no idea what the state is 
    // because only our update loop knows about it 
}); 

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

window.addEventListener('click', function() { 
    const state = getState(); 

    createState({ 
    player, 
    clicks: clicks + 1 
    }); 
}); 

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

Кроме того, я полагаю, я мог бы добавить событие щелчка в очереди действий, подлежащих обработке в течение цикла обновления, что-то вроде:

window.addEventListener('click', function() { 
    createAction('click', e); 
}); 

function update(state, actions) { 
    const newState = { 
    player, 
    ticks: state.ticks + 1, 
    clicks: state.clicks + actions.clicks.length 
    }; 

    setTimeout(update.bind(this, newState, []), 100); 
} 

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

Как выглядит дизайн системы, когда есть несколько асинхронных источников событий, и мы хотим, чтобы все было неизменным? Или, по крайней мере, что является хорошей моделью для управления изменчивостью в такой системе?

+6

Вы ищете функциональное реактивное программирование (FRP). – Bergi

+3

Эта очередь действительно звучит как разумный способ (просто не забудьте очистить ее после того, как вы ее использовали в 'update'). Но на самом деле вам всегда нужно, чтобы небольшая часть вашей программы была изменчивой, потому что событие, подобное щелчку *, должно иметь побочный эффект (или вообще ничего не делает). Immutabiltity не о том, чтобы никогда ничего не делать в вашей программе, это разумное моделирование того, что вы делаете. – Bergi

+1

Согласен с @Bergi. События по определению являются состояниями; вход, по определению, изменчив. Лучшее, что вы можете сделать в своем случае, - попытаться изолировать изменчивое состояние от неизменяемого состояния как можно больше. Очередь действительно работает хорошо (особенно с точки зрения игры, я сам ее использовал), и поэтому в вашем случае очередь может быть изменчивой - вы можете продолжать добавлять события - UNTIL запускается следующее обновление. В этот момент вам нужно сделать неизменяемую копию очереди. После этого вы можете придерживаться непреложных конструкций. –

ответ

4

Возможно, вас заинтересует Redux. Аналогичным является подход Redux:

  • Он моделирует состояние всего приложения как единый неизменный объект.
  • Действия пользователя - это, по существу, сообщения, отправленные на store для произвольной обработки.
  • Действия обрабатываются редуктор функции в форме f(previousState, action) => newState. Это более функциональный подход, чем ваша оригинальная версия.
  • store управляет редукторами и поддерживает одно неизменное состояние приложения.

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

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

0

Используя Object.freeze, вы можете сделать объект неизменен:

var o = {foo: "bar"}; 
Object.freeze(o); 
o.abc = "xyz"; 
console.log(o); 

даст {foo: "bar"}. Обратите внимание, что попытка установить новое свойство в замороженной переменной будет сбой молча.

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

+0

Я думаю, вы, возможно, неправильно поняли вопрос. Я полностью осведомлен о том, как создавать и управлять неизменяемыми данными. Речь идет о отслеживании состояния приложения между несколькими асинхронными событиями. –

1

В попытке непосредственно ответить на ваш вопрос:

«Что делает дизайн для системы выглядеть, когда есть несколько источников асинхронного события, и мы хотим, чтобы все было непреложным Или, по крайней мере, то, что это хорошо шаблон для управления изменчивостью в такой системе? "

Правильный шаблон решения для этого дизайна в мире unix был асинхронным очередью сообщений FIFO (AMQ), начиная с System 5, в то время как теоретически существуют условия, при которых условия гонки и неопределенность состояния могут возникать в Практика почти никогда не бывает. На самом деле, ранние исследования надежности AMQ определили, что эти ошибки возникли не из-за задержки передачи, а из-за столкновения с синхронными запросами прерывания, поскольку ранние AMQ были по существу просто каналами, реализованными в пространстве ядра. Современное решение, фактически решение Scala, заключается в реализации AMQ в общей защищенной памяти, тем самым устраняя медленные и потенциально опасные вызовы ядра.

Как выясняется, если общая пропускная способность сообщения меньше общей пропускной способности канала, а расстояние передачи меньше, чем светлая секунда - сопротивление/переключение, вероятность отказа является космически низкой (например, что-то вроде порядка 10^-24). Есть всевозможные теоретические причины, почему, но без отступления от квантовой физики и теории информации это не может быть действительно рассмотрено здесь кратко, однако пока еще не найдено математического доказательства, чтобы окончательно доказать, что это так, это все оценки и практика. Но каждый аромат unix полагался на эти оценки уже более 30 лет для надежной асинхронной связи.

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

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

Наконец, если вам нужно иметь дело с большими или пакетированными сообщениями, перемещающимися на большом расстоянии от извилистой сети или в ядре и из него, шаблон дизайна должен добавить исходный адрес для обратных вызовов и временную метку, а также небольшая обработка коррекции, поэтому TCP/IP, SMQ, Netbios и т. д. включают их в свои протоколы, поэтому вам нужно сделать так, чтобы вы изменили свою приоритетную/порядок очередей, чтобы быть осведомленными о пакетах.

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

Надеюсь, я ответил на ваш вопрос и не отклонился от того, что вы просили. :)

Post-Edit:

Вот некоторые хорошие примеры того, как и почему использовать эти виды родов конструкций очередей для распределенных параллельных приложений, они используются для сердца большинства FRP распространена дизайнерских решений :

https://docs.oracle.com/cd/E19798-01/821-1841/bncfh/index.html

https://blog.codepath.com/2013/01/06/asynchronous-processing-in-web-applications-part-2-developers-need-to-understand-message-queues/

http://www.enterpriseintegrationpatterns.com/patterns/messaging/ComposedMessagingMSMQ.html

http://soapatterns.org/design_patterns/asynchronous_queuing

http://www.rossbencina.com/code/programming-with-lightweight-asynchronous-messages-some-basic-patterns

http://www.asp.net/aspnet/overview/developing-apps-with-windows-azure/building-real-world-cloud-apps-with-windows-azure/queue-centric-work-pattern

http://spin.atomicobject.com/2014/08/18/asynchronous-ios-reactivecocoa/

http://fsharpforfunandprofit.com/posts/concurrency-reactive/

и видео из Одерски ...

https://www.typesafe.com/resources/video/martin-odersky---typesafe-reactive-platform

:)

Смежные вопросы