Clojure состояния внутри состояний внутри состояний
Мне бы хотелось услышать, какие советы здесь есть у гуру Clojure об управлении состоянием в иерархиях. Я нахожу, что часто использую {:structures {:like {:this {:with {:many 'levels}} } } }
, и если я хочу отслеживать изменения состояния на нескольких уровнях, бросая атомы вокруг значений (atom {:like (atom 'this)} )
, я считаю, что это должно быть неправильно. Как правило, лучше использовать только один атом на верхнем уровне и не иметь никаких значений на карте?
Ответы
Ответ 1
Не используйте вложенные атомы в структуре данных, если это вообще возможно.
Основная причина в том, что неизменность - ваш друг. Clojure - функциональный язык, который процветает в неизменных структурах данных. Большинство библиотек предполагают неизменные структуры данных. Clojure STM предполагает неизменные структуры данных, чтобы получить максимально возможный concurrency. Неизменность дает вам возможность делать последовательные снимки всего состояния в любой момент. Чистые функции, которые работают с неизменяемыми данными, легко разрабатываются и тестируются.
Если вы помещаете атомы в свои структуры данных, вы теряете все преимущества неизменности и риска, делая ваш код очень сложным - гораздо сложнее рассуждать о структуре данных, если он содержит много изменяемых компонентов.
Некоторые предлагаемые альтернативные подходы:
- Поместите всю свою структуру данных в один ref или атом. Это может быть огромная структура данных без проблем - я однажды написал игру, в которой вся карта игры была проведена в одном атоме без каких-либо трудностей.
- Используйте различные методы, предназначенные для доступа и изменения вложенных неизменяемых структур данных: assoc-in, get-in, update-in и т.д.
- Используйте рекурсивные функции, чтобы сделать вашу структуру данных более управляемой. Если у одной node вашей структуры есть суб-узлы одного и того же типа, то обычно это хороший намек на то, что вы должны использовать какую-то рекурсивную функцию.
Ответ 2
Вы можете использовать функции assoc-in
, get-in
, update-in
и dissoc-in
для работы с вложенными структурами.
Они очень удобны, но я не знаю, могут ли они обрабатывать атомы и так напрямую. В худшем случае вы должны иметь возможность вложить их в deref, например:
(def m (atom {:like {:this {:nested (atom {:value 5})}}}))
@(get-in @m [:like :this :nested])
; => {:value 5}
(get-in @(get-in @m [:like :this :nested]) [:value])
; => 5
Вы можете использовать ->
, чтобы сделать это более читаемым:
(-> @m
(get-in [:like :this :nested])
deref
(get-in [:value]))
; => 5
Относительно вложенных атомов/refs/агентов и т.д. Я думаю, это зависит от того, чего вы пытаетесь достичь. Разумеется, легче рассуждать о вещах, если только один из них наверху и синхронизация изменений.
С другой стороны, если вам не нужна эта синхронизация, вы тратите время на это, и вам будет лучше с вложенными атомами /refs/agents.
Суть в том, что я не думаю, что любой способ "правильный путь", оба они имеют свои привычки.
Ответ 3
Вот сообщение в блоге от Christophe Grand wrestling со сложностями этой установки: http://clj-me.cgrand.net/2011/10/06/a-world-in-a-ref/
Ответ 4
Я бы предпочел использовать один атом на верхнем уровне, так как это сделало бы вещи действительно простыми, а также означало бы, что данные представляют состояние, которое одновременно изменяется на все операции. Если вы поместите атомы на каждом уровне, то это станет слишком сложным, чтобы понять, что происходит. Также, если в вашем случае вложенность идет слишком глубоко, я предлагаю вам сесть и тщательно подумать, нужна ли вам такая структура или может быть какая-либо альтернатива, потому что это, безусловно, приведет к сложности, пока вложенные данные не будут рекурсивными (т.е. такая же структура на каждом уровне)