Как стать автором
Обновить

Комментарии 36

Вот так легко и непринужденно function sayHello() { alert('Привет, ' + localStorage.name + '!'); } растянулся на 28 строк кода :)
Не, там же надо было обновлять текст надписи когда имя юзера меняется.
Да понятно, я же шучу — работа проделана автором действительно гигантская.
Спасибо большое за публикацию!
Понравился ваш стиль повествования — тема развивается очень гармонично,
читается ясно и просто, вопросы по ходу раскрываются.
Пишите еще про frp, с удовольствием ознакомлюсь.
> 5. Асинхронность

д) почему бы атому не вернуть deferred/promise, очевидный и родной для асинхронщины?
Потому что promise — это, по сути, тоже атом, но способный сменить состояние только 1 раз. Смысл возвращать один атом из другого?

Если вернуть promise — то вызывающий код будет вынужден подписаться аж на два события — во-первых, событие change атома — а во-вторых, событие done обещания. Это точно будет удобно?
Достаточно будет только done обещания.

Ну и, пожалуй, это будет удобнее, чем дефолтное значение/закэшированное/undefined/исключение, после котрых всё либо приходит в неконсистентность, либо нужно догадываться совершать дополнительные действия.
Если done обещания нам достаточно, то зачем нам вообще атомы? Использовать атомы бессмысленно, если не подписываться на change, прямо или косвенно (через автотрекинг).
Не нужно никаких действий. Вся технология рассчитана на то, что атомы могут меняться сами в зависимости от тех или иных неизвестных нам причин. Так какая, нафиг, разница: изменил пользователь своё имя сам или строка с его именем «Вася Пупкин» пришла, наконец-таки, из облака? Любая программа обрабатывающая первую проблему (а для решения её, напомним, и созданы атомы) автоматически способна решить вторую (обратное неверно).
1. Атом — обобщение над обещаниями, так что ничто не мешает ему поддерживать их интерфейс. Для интеграции с императивным кодом это действительно удобно.

2. Атом абстрагирует от асинхронщины, позволяя в любой момент прервать вычисление не потеряв проделанной работы, а потом продолжить, когда нужные данные появятся, не начиная всё с начала.
Спасибо, интересно!

А если последовательно отделять функциональную (повторяющуюся) логику от бизнес-логики — код несколько упрощается. Я к тому, что дополнительный слой иерархии в «атомах», если его вести честно, делает жизнь намного проще.

И, что интересно, кода получается меньше. Прям намного меньше.
Теперь я знаю, как называется тот подход, который я применяю уже пару лет)

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

Но атомы не отменяют события и промисы. Если есть необходимость просто получить данные, то атомы избыточны и даже вредны.
По моему опыту «просто получить данные» — достаточно редкий случай. Обычно надо не просто получить, но и закешировать. А если закешировали, то и вовремя инвалидировать кэш, а потом снова грузить. Так что обещания использовать нет никакого смысла, если есть возможность использовать атомы.
Это очень сильно зависит от задачи, на самом деле. Грубо говоря, данные можно поделить на 2 категории: доменные, которые живут постоянно и описываются атомами, и контекстно-зависимые. Соотношение очень сильно зависит от типа системы. В корпоративном приложении или b2c системах, типа яндекс денег, практически не будет контекстных данных. В соц. сетях, наоборот, 90% времени это просмотр чужих страниц, котиков и т.п., которые существуют только в рамках текущего запроса пользователя. Их просто бессмысленно кэшировать и, более того, это прямой путь к неработоспособности на слабых клиентах. В любом случае, тут разумно использовать совершенно другие механизмы кэширования с меньшими издержками по памяти и производительности.
Я бы не разделял эти две категории. В любой соц сети есть навигация, чаты под каждым котиком, отображение лайков в реальном времени, куча разнообразных кнопочек: пожаловаться, удалить из ленты, восстановить, приглушить свет на всём сайте, кроме этой фоточки и тд и тп. И это всё состояния. А кэш — это лишь частный случай состояния, которое может очищаться, когда ничего зависимого от него не отображается на экране.
Мы с вами немного про разные вещи говорим.

Вы про UI, я про обертку над API. По сути, с вами согласен, если говорить именно про UI слой.
Ну так UI использует обёртку над API (модель), которая кэширует и буферизирует запросы и инвалидирует кэш, когда данные меняются. И чтобы всё это отражалось на UI обёртка тоже должна быть реактивной.
Поняв проблему легко и придумать решение: достаточно при линковке атомов запоминать максимальную глубину среди ведущих плюс один, а при итерировании по отложенным для обновления атомам в первую очередь обновлять атомы с меньшей глубиной.
Начав ради интереса делать свою реализацию под C#, я столкнулся с такой проблемой: при смене списка зависимостей в результате работы автотрекинга может измениться глубина. На уменьшение глубины можно забить, а вот увеличение глубины надо распространять всем мастерам вверх по графу. В итоге, мы возвращаемся к той же самой проблеме, с которой и начинали: глубина может обновляться много раз у одного и того же атома. Конечно, операция обновления глубины более дешевая, чем операция пересчета состояния атома (поскольку вторая может и запрос на сервер выполнять) — но все равно лишние квадраты в алгоритмах мне не нравятся.

К счастью, решение я нашел очень простое, не требующее существенной переделки архитектуры и даже упрощающее код: надо глубину вычислять не при линковке, а непосредственно при помещении атома в очередь.
Видимо я плохо осветил этот момент. Глубина может измениться и как правило это делает, но это не важно, пока атом не помещается в очередь на обновление. А он туда не помещается, поскольку только что актуализировал своё состояние. И все атомы меньшей глубины тоже его актуализировали. Так что следующий атом в очереди может смело вычислять своё значение и быть уверенным, что атомы с меньшей глубиной не изменятся, если он конечно сам не полезет их зачем-то менять.
То есть вы тоже вычисляете глубину в момент помещения атома в очередь?
Нет, в момент линковки ведомый спрашивает у ведущего его глубину и сравнивает со своей.Если своя глубина не больше глубины ведущего, то устанавливается на 1 больше, чем у него. А при помещении в очередь атом ставится в очередь соответствующей своей глубине на момент помещения.
Что-то я запутался. Жду тогда библиотеку, чтобы протестировать…
Если кому-то интересна реализация атомов для C# — можете глянуть мою. К сожалению, мне лень делать тесты, так что ошибки там наверняка есть. Но я обещаю быстро исправить все найденные.
К сожалению из-за искромётной подачи, мало кто вникает в суть доклада: www.youtube.com/watch?v=R4sTvHXkToQ
> Для описанного выше случая атом A пересчитает своё значение ровно один раз причём сделает это по окончании текущего обработчика события, то есть после всех внеснных нами изменений в атомы B, C и любые другие.

Получается, что в текущем обработчике мы не сможем использовать атом А, т. к. он будет иметь значение, не согласованное с В и С. Или всё-таки нет?
Если нам надо использовать значение ровно одного атома — то оно всегда будет согласовано с самим собой. Если же нам надо использовать значения нескольких атомов — то надо делать новый атом, использующий те значения — и возвращающий ровно одно. Этот атом будет обновляться только в те моменты времени, когда его входы будут согласованы — ну а вызывающий код вернулся к случаю номер 1.

Тут возможна вторая проблема — в случае автотрекинга зависимостей, атом может обратиться к другому атому, к которому он не обращался раньше — и состояние которого еще не согласовано с остальными входами. В таком случае надо не отдавать старое значение — а дождаться пока запрошенный атом довычислится. Посмотрим, удалось ли автору справиться с этим случаем. Мне вот, к примеру, в своей библиотеке этого сделать не удалось…
Он будет помечен как «не актуальный», а значит при обращении актуализирует своё значение на основе B и C. Тут есть более сложный случай: D зависит от А, А только что запланировал к обновиться, а значит D ещё не знает, что ему тоже потребуется обновление (а может и не потребуется, А-то ещё не вычислился). Поэтому, если мы тут же обратимся к D, то получим старое значение, которое вскоре возможно(!) изменится.
Если у нас есть статус «не актуальный», то логично его продвигать вверх по иерархии до конца. То есть, если у нас A зависит от B и C, а D зависит от A, то при изменении B не актуальными должны становиться и A и D.

А что, действительно реальна ситуация, когда обновляются 2000 атомов, от которых зависит 2001-й?
Тогда у нас получится что на каждый чих у нас будет обновляться всё приложение, что гораздо хуже.

Конечно, выводим список на 2000 элементов с фильтрацией по какому-либо параметру — получаем одну зависимость от самого списка и 2000 зависимостей от соответствующего параметра каждого из элементов. При изменении параметра у любого из элементов список перефильтруется. Это может показаться расточительным, ведь можно просто проверить подходит изменившийся элемент под фильтр и либо добавить, либо удалить. Но когда к фильтрации добавляется сортировка, группировка, да ещё и иерархия с разворачиванием ветвей и дублированием узлов, то задача «поместить в нужное место списка» становится неоправданно сложной.
> Так как чуть ли не каждый слот памяти заворачивается в атом, то реализация атомов должна быть максимально быстрой и компактной.

Сразу напомнило Datomic — это очень приятная база данных, похожая на RDF, OrientDB или Node4j — только с ещё более простыми и фундаментальными концепциями
Ух ты, я уж было собрался запилить свою иммутабельную графовую субд, а её уже оказывается пилят. Спасибо :-)
Хотя… это всего лишь фронтенд к другим субд со всеми их болезнями.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории