Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   $jin.atom - frp всегда с тобой (https://javascript.ru/forum/project/44781-%24jin-atom-frp-vsegda-s-tobojj.html)

tenshi 01.02.2014 23:59

$jin.atom - frp всегда с тобой
 
$jin.atom – компактная (4KB в сжатом виде) библиотека, предоставляющая класс для создания атомов – минимальных частиц FRP-приложений. FRP – функциональное реактивное программирование. Суть его в том, что мы описываем зависимости между данными в виде функций и рантайм автоматически поддерживает актуальность всех значений. Атом хранит в себе ровно одно значение, а также всю необходимую информацию о зависимостях (кто от него зависит, от кого он зависит, как вычислить значение и тп).

Отличительные особенности библиотеки:
• простота реализации
• удобство интеграции с другими парадигмами
• автоматическое выявление зависимостей

Общие сведения

Атомы бывают 3 типов:

Источники (sources) – атомы, которые ни от кого не зависят – их значение меняется императивно в зависимости от каких-либо внешних факторов (например, действия пользователя или пришедшие данные с сервера).

Покрывала (covers) – атомы, которые умеют не только функционально вычислять своё значение, но и императивно его куда-нибудь передавать. Такие используются как конечные звенья дерева зависимостей и как правило от них никто не зависит.

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

Зависимости между атомами строятся автоматически в момент вычисления из значений. Каждый атом хранит в себе:

• список хозяев (masters) – атомов, от значений которых непосредственно зависит значение текущего
• список рабов (slaves) – атомов, значения которых непосредственно зависят от значения текущего
• номер слоя (slice) – на 1 больше, чем номера слоёв всех хозяев, или иначе – максимальная дистанция до источников.

Каждый раз, когда реально меняется значение мастера (сравнение идёт через оператор идентичности `===`) – все рабы получают распоряжение об обновлении, но делают они это не сразу, а отложенно. Причем, чем меньше номер слоя, тем раньше будет вычислено значение атома. Это сделано для того, чтобы не приходилось несколько раз пересчитывать значение атома, пока меняются значения его хозяев.

Несколько примеров

Таймер, каждую секунду увеличивающий своё значение. Начинает работать лишь когда кто-либо запрашивает его значение.
var tick = $jin.atom(
    {   pull: function( prev ){
            setTimeout( function( ){
                tick.pull()
            }, 1000 )
            return ( Number( next ) || 0 ) + 1
        }
    })

Пример очень простой, не повторяйте это дома (как минимум не хватает возможности его остановки), но для иллюстрации подойдёт.

Отобразим текущее значение таймера в заголовке окна.
var title = $jin.atom(
    {   pull: function( ){
            return 'Current tick: ' + tick.get()
        }
    ,   push: function( next ){
            document.title = next
        }
    })
    title.update()

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

var $my = { timer: {} }
    
    $jin.atom.prop({ '$my.timer.tick':
    {   pull: function( prev ){
            setTimeout( function( ){
                $my.timer.tickAtom().pull()
            }, 1000 )
            
            return ( Number( next ) || 0 ) + 1
        }
    }})
    
    $jin.atom.prop({ '$my.timer.title':
    {   pull: function( ){
            return 'Current tick: ' + this.tick()
        }
    ,   push: function( next ){
            document.title = next
        }
    }})
    
    $my.timer.title()

Ещё один пример – простенькое слайдшоу: http://nin-jin.github.io/slide/

Альтернативы

KnockOut: http://knockoutjs.com/

− Недостатки
• Большой объём. В 10 раз больше исходного кода.
• Плохая интеграция с прототипным наследованием. Каждый observable (атом) – это отдельное замыкание. Все observable-свойства создаются в конструкторе вьюшки, а не по мере необходимости. Это потребляет лишние ресурсы.

± Спорные моменты
• В комплекте идёт функционал формирования дом-дерева и биндинга к его узлам. Это отдельный, большой функционал, который не всегда необходим.

Подробнее тут: http://hyoo.ru/?article=Атомы+на+JS;author=Jin
Собранная либа: https://github.com/nin-jin/nin-jin.g...e%3Drelease.js

tenshi 02.02.2014 00:08

Критика очень приветствуется :write:

nerv_ 03.02.2014 20:14

Цитата:

Сообщение от tenshi
Суть его в том, что мы описываем зависимости между данными в виде функций и рантайм автоматически поддерживает актуальность всех значений

кто поддерживает?

Цитата:

Сообщение от tenshi
Каждый раз, когда реально меняется значение мастера (сравнение идёт через оператор идентичности `===`) – все рабы получают распоряжение об обновлении, но делают они это не сразу, а отложенно. Причем, чем меньше номер слоя, тем раньше будет вычислено значение атома. Это сделано для того, чтобы не приходилось несколько раз пересчитывать значение атома, пока меняются значения его хозяев.

что, если нижний слой влияет на верхний? Получаются те же самые бесконечные вычисления

Написано сложно. Зачем писать сложно о сложном? Так любой сможет. А ты попробуй просто напиши.

С FRP дела не имел.

Что я "вижу" по сути (код не глядел):

function Atom() {
   this.children = [];
}

// уведомить об изменении
Atom.prototype.notify = function() {
   // цикл по детям, детям детей и т.д.
};

// создаем родительский atom
var atom = new Atom();
// формируем цепочку детей (зависимостей)
atom.children.push(new Atom());

// какое нибудь изменение, а в след за ним уведомление
atom.notify();
или
вводим id-шники в рамках атомов, с помощью которых можно подписаться или отслеживать любые изменения любых атомов и формировать зависимости. Все :)

tenshi 03.02.2014 23:44

> кто поддерживает?

В данном случае представляемая библиотека

> что, если нижний слой влияет на верхний? Получаются те же самые бесконечные вычисления

Нет, будет исключение, ибо это явная ошибка в консерватории)

> Написано сложно. Зачем писать сложно о сложном? Так любой сможет. А ты попробуй просто напиши.

Это и есть просто, на сколько это возможно)

> // формируем цепочку детей (зависимостей)
> atom.children.push(new Atom());

Вот именно этого ручного связывания и хотелось бы избежать.

Смотри, пусть у нас есть такое определение свойства:

$jin.atom.prop({ '$jin.task.view.item..current':
{   pull: function( ){
        return this.list().task() === this.task()
    }
}})


Что тут происходит:
1. функция - это формула вычисления значения
2. когда кто-либо запрашиват значение атома, этот атом добавляет себя в стек слушателей и исполнятет указанную функцию для вычисления значения
3. this.list - это тоже атомное свойство, когда мы запрашиваем ее значение привязанный к ней атом смотрит в стек и связывает себя с атомом с вершины стека, таким образом атом свойства this.current становится слушателем изменений атома свойства this.list
4. this.list() возвращает объект и у этого объекта есть атомное свойство task - аналогично происходит подписка и на него.
5. Ну и в завершении происходит подписка на this.task()

Но сама прелесть в том, что подписки тоже всегда актуальны:

$jin.atom.prop({ '$jin.task.view.item..current':
{   pull: function( ){
        if( this.disabled() ) return false
        return this.list().task() === this.task()
    }
}})


Если в какой-то момент свойство this.disabled станет равно true, то при следующем вычислении this.current ее атом будет отписан от всех атомов кроме атому this.disabled, потому что пока свойство this.disabled не изменится не изменится и значение this.current

nerv_ 07.02.2014 17:18

tenshi, спасибо за разъяснения :)

tenshi 14.03.2014 21:09

Кому интересно, сравнение скорости с КО: http://jsperf.com/reactive-libraries-comparison

И диаграмка распространения изменений, чтобы понять почему такая большая разница: http://nin-jin.github.io/slide/#slide=induction

nerv_ 15.03.2014 00:18

Цитата:

Сообщение от tenshi
И диаграмка распространения изменений, чтобы понять почему такая большая разница

и чего она отражает?

Цитата:

При больших скоупах это довольно затратно. Пруфлинк http://jsfiddle.net/7sj76/8/.
Мой "пруфлинк" http://jsfiddle.net/7sj76/17/

tenshi 12.04.2014 17:01

Показывает как происходят вычисления при изменении нескольких моделей.

К чему это?

tenshi 20.04.2014 21:17

Добавил мануал по реактивным вьюшкам: http://hyoo.ru/?article=%D0%A0%D0%B5...F;a uthor=Jin

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

tenshi 20.04.2014 21:42

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



Часовой пояс GMT +3, время: 15:19.