Реактивное программирование и его реализация

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

Определения реактивного программирования аналогичны математическим и противопоставляют процедурному (или императивному) программированию, описывающему списки команд.

Например, когда на классическом процедурном языке пишется::

a = b + c

Значение a зависит не от b и c, а от значений, которые они имеют, когда процессор встречается с этой строкой. Она изменится только с новым назначением переменной а.

В реактивном программировании a становится зависимым от b и c на время сеанса. Каждое изменение значения этих переменных приводит к изменению значения a.

А если потом мы зададим:

b = d + e

значение а также косвенно будет зависеть от значения d и e.

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

Реактивный подход может создать трудности. Таким образом, если определить:

d = a + z

Поскольку опосредованно зависит от d и d зависит от a, значение a становится трудно определить. Это та же проблема, что и в процедурном языке, если в теле функции f1 возникает вызов функции f2, а в теле f2 - вызов f1.
Эта операция является абсолютно допустимой, если в зависимости от результата добавляется условие окончания, если она не входит в бесконечный цикл. Это форма рекурсии.

При переносе косвенной рекурсивности на уровень PR можно указать, что a = b и b = a + z, если одно из двух определений имеет условие, первое - значение b, второе - значение a.
Но если объекты модели являются транспозицией реального мира, это не обязательно необходимо. Это равносильно включению в кодекс взаимозависимости, которая существует в реальном мире. Что на самом деле является целью. Но это может работать только в конкурирующей системе, в противном случае эти два определения, работающие в бесконечном цикле, монополизировали бы все ресурсы процессора.

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

Когда лучше использовать реактивное программирование?

Каждый раз проблему выражать сложнее, чем решение.

Возьмем случай с системой бронирования. Это описывается как набор полей бронирования, с одной стороны, список ресурсов, которые выделяют слоты, с другой - очередь клиентов, которые просматривают флажки для выбора уже не зарезервированного расписания. Каждый флажок устанавливается как пустой, или выделенный, или зарезервированный, и каждое значение зависит от внешнего события, инициированного ресурсом или клиентом. Часть «процесса» проста, и сводится к регистрации записей ресурсов и клиентов.

С другой стороны, возьмем случай, когда у нас есть очень большой список слов, которые нужно сортировать как можно быстрее: проблема проста, слова должны быть в алфавитном порядке. Для того чтобы достичь этого результата быстрее всего, используются такие алгоритмы, как Quick Sort или Insert Sort, поэтому здесь лучше подходит процедурное программирование.

Так что императивный режим лучше подходит для выражения процессов и преобразований и декларативный режим взаимодействий.

Реактивное программирование против императивного

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

Реактивное программирование против функционального

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

И против функционального реактивного программирования

Это другая парадигма. Это - добавление ориентированной на события модели к функциональному языку, что было реализовано, например, в FrTime, разработанном на основе Scheme. В отличие от чистого функционального программирования, некоторые объекты или переменные изменяемы, но их состояние не зависит от назначений в теле программы, что вернёт нас к императивному программированию, зависит от событий.

Реактивное программирование против событий

Пиар на самом деле довольно близок к работе событий, которые вписываются в процедурный язык вроде JavaScript. Но она отличается тем, что позволяет писать более лаконичный и четкий код.
Если можно написать var A = B + C, Это проще, чем связать событие с A, которое само зависит от событий, связанных с B и C. Можно связать с A событие, которое зависит от результата суммы значений B и C и событий, меняющих значение B и C. Но когда одно из этих событий включено, это не активирует сумму их значений. Для этого необходимо, чтобы события вызывали функцию, которая вычисляет эту сумму, а функция запускала другое событие при получении результата, которое перехватывается A.
PR может выражаться в PE так же, как может быть в PI, поскольку в любом случае он сводится к машинному коду, который имеет императивную форму, но его преимущество в том, что сложность кода передается компилятору и runtime.

Реактивное программирование и конкуренция

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

Реактивное программирование на языках

Пиар не реализуется непосредственно ни на одном популярном языке, даже на последних. Ее нет ни в «Го», ни в «Русте», ни в «Дарте». Только Пролог использует что-то близкое со своим логическим программированием.
Но его пытаются добавить в виде расширения на процедурные языки.
В Java обработчики событий назначаются свойствам, и то же самое можно сделать в JavaScript. События могут связываться как определения PR и давать косвенные результаты.
Но с простотой кода RP нельзя сравнивать эти процессы и переплетение JavaScript callbacks.

Для JavaScript. React.js в основном предназначен для построения графического интерфейса. Он использует события и довольно мало похож на пиар. Статья «Истинное реактивное программирование в JavaScript» на этом сайте объясняет, как очень просто добавить PR в JavaScript.

Натрий - библиотека, называющая себя PR-расширением для Java и C++.
Reactive Extension - это модуль C # для программирования на основе событий.

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

Как его реализовать

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

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

Компилируя реактивный язык в код JavaScript и не нацеленный на производительность, можно упростить, используя события в бэкенде, с кодом, который генерирует определения PR в функциях и связывает эти функции с каждым событием, как описано выше (PR vs PE).

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

Денис Суро 23 января 2014 года.