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

Реактивное программирование было впервые разработано Гленн Вадден в 1986 г.[1] как язык программирования (VTScript[2]) в системе диспетчерского контроля и сбора данных (SCADA ) промышленность.

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

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

Другой пример - язык описания оборудования Такие как Verilog, где реактивное программирование позволяет моделировать изменения по мере их распространения по цепям.[нужна цитата ]

Реактивное программирование было предложено как способ упростить создание интерактивных пользовательских интерфейсов и системной анимации почти в реальном времени.[нужна цитата ]

Например, в модель – представление – контроллер (MVC), реактивное программирование может способствовать внесению изменений в базовый модель которые автоматически отражаются в связанном Посмотреть.[3]

Подходы к созданию реактивных языков программирования

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

Модели и семантика программирования

Семейство реактивного программирования управляется множеством моделей и семантики. Мы можем условно разделить их по следующим размерам:

  • Синхронность: является ли базовая модель времени синхронной или асинхронной?
  • Детерминизм: детерминированный или недетерминированный как в процессе оценки, так и в результатах
  • Процесс обновления: обратные вызовы против потока данных против актера

Методы реализации и проблемы

Суть реализаций

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

Изменить алгоритмы распространения

Наиболее распространенные подходы к распространению данных:

  • Тянуть: Ценный потребитель на самом деле активныйв том, что он регулярно запрашивает значения у наблюдаемого источника и реагирует всякий раз, когда релевантное значение доступно. Такая практика регулярной проверки событий или изменений значений обычно называется опрос.
  • Толкать: Потребитель значения получает значение из источника всякий раз, когда значение становится доступным. Эти значения самодостаточны, например они содержат всю необходимую информацию, и потребителю не нужно запрашивать дополнительную информацию.
  • Тяни-Толкай: Потребитель ценности получает уведомление об изменении, которое представляет собой краткое описание изменения, например "какое-то значение изменилось" - это толкать часть. Однако уведомление не содержит всей необходимой информации (т.е. не содержит фактических значений), поэтому потребитель должен запросить у источника дополнительную информацию (конкретное значение) после того, как он получит уведомление - это тянуть часть. Этот метод обычно используется, когда существует большой объем данных, которые могут быть потенциально интересны потребителям. Таким образом, чтобы уменьшить пропускную способность и время ожидания, отправляются только легкие уведомления; а затем те потребители, которым требуется дополнительная информация, будут запрашивать эту конкретную информацию. У этого подхода также есть недостаток, заключающийся в том, что источник может быть перегружен многочисленными запросами дополнительной информации после отправки уведомления.

Что толкать?

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

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

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

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

Есть два основных способа строительства дома. граф зависимостей:

  1. График зависимостей поддерживается неявно в пределах цикл событий. Регистрация явных обратных вызовов приводит к созданию неявных зависимостей. Следовательно, инверсия управления, который вызывается обратным вызовом, остается на месте. Однако для того, чтобы сделать обратные вызовы функциональными (т.е. вернуть значение состояния вместо значения единицы), необходимо, чтобы такие обратные вызовы стали композиционными.
  2. График зависимостей зависит от программы и создается программистом. Это облегчает адресацию обратного вызова инверсия управления двумя способами: либо указан график явно (обычно с использованием предметно-ориентированный язык (DSL), который может быть встроен), или граф неявно определяется с помощью выражения и генерации с использованием эффективных, архетипических язык.

Проблемы реализации в реактивном программировании

Глюки

При распространении изменений можно выбрать такие порядки распространения, чтобы значение выражения не было естественным следствием исходной программы. Мы можем легко проиллюстрировать это на примере. Предполагать секунды является реактивным значением, которое изменяется каждую секунду, чтобы представить текущее время (в секундах). Рассмотрим это выражение:

t = секунды + 1g = (t> секунды)
Реактивное программирование glitches.svg

Потому что т всегда должно быть больше, чем секунды, это выражение всегда должно возвращать истинное значение. К сожалению, это может зависеть от порядка оценки. Когда секунды изменения, необходимо обновить два выражения: секунды + 1 и условный. Если первое выполняется раньше второго, то этот инвариант будет сохраняться. Однако, если условие обновляется первым, используя старое значение т и новое значение секунды, тогда выражение вернет ложное значение. Это называется Сбой.

Некоторые реактивные языки не содержат сбоев и подтверждают это свойство.[нужна цитата ]. Обычно это достигается топологическая сортировка выражения и обновление значений в топологическом порядке. Однако это может иметь последствия для производительности, например задержку доставки значений (из-за порядка распространения). Поэтому в некоторых случаях реактивные языки допускают сбои, и разработчики должны осознавать возможность того, что значения могут временно не соответствовать исходному тексту программы и что некоторые выражения могут оцениваться несколько раз (например, t> секунд может оцениваться дважды: один раз, когда новое значение секунды прибывает, и еще раз, когда т обновления).

Циклические зависимости

Топологическая сортировка зависимостей зависит от того, является ли граф зависимостей ориентированный ациклический граф (DAG). На практике программа может определять граф зависимостей с циклами. Обычно языки реактивного программирования ожидают, что такие циклы будут «разорваны» путем размещения некоторого элемента вдоль «задней кромки», чтобы разрешить завершение реактивного обновления. Обычно языки предоставляют такой оператор, как задерживать который используется для этой цели механизмом обновления, поскольку задерживать подразумевает, что последующее должно быть оценено на «следующем временном шаге» (позволяя завершить текущую оценку).

Взаимодействие с изменяемым состоянием

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

В некоторых случаях возможны принципиальные частичные решения. Два таких решения включают:

  • Язык может предложить понятие «изменчивой ячейки». Изменяемая ячейка - это ячейка, о которой знает система реактивного обновления, поэтому изменения, внесенные в ячейку, распространяются на остальную часть реактивной программы. Это позволяет нереактивной части программы выполнять традиционную мутацию, в то же время позволяя реактивному коду знать об этом обновлении и реагировать на него, таким образом поддерживая согласованность взаимосвязи между значениями в программе. Примером реактивного языка, который предоставляет такую ​​ячейку, является FrTime.[4]
  • Правильно инкапсулированные объектно-ориентированные библиотеки предлагают инкапсулированное понятие состояния. В принципе, поэтому такая библиотека может плавно взаимодействовать с реактивной частью языка. Например, обратные вызовы могут быть установлены в геттерах объектно-ориентированной библиотеки, чтобы уведомлять механизм реактивного обновления об изменениях состояния, а изменения в реактивном компоненте могут быть отправлены в объектно-ориентированную библиотеку через геттеры. FrTime использует такую ​​стратегию.[5]

Динамическое обновление графика зависимостей

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

t = if ((секунды mod 2) == 0): секунды + 1 else: секунды - 1 endt + 1

Каждую секунду значение этого выражения меняется на другое реактивное выражение, которое т + 1 тогда зависит от. Поэтому график зависимостей обновляется каждую секунду.

Разрешение динамического обновления зависимостей обеспечивает значительную выразительную силу (например, динамические зависимости обычно возникают в графический интерфейс пользователя (GUI) программы). Однако механизм реактивного обновления должен решить, восстанавливать ли выражения каждый раз или оставить узел выражения созданным, но неактивным; в последнем случае убедитесь, что они не участвуют в вычислениях, когда они не должны быть активными.

Концепции

Степени явности

Реактивные языки программирования могут варьироваться от очень явных, где потоки данных настраиваются с помощью стрелок, до неявных, когда потоки данных являются производными от языковых конструкций, которые похожи на конструкции императивного или функционального программирования. Например, в неявно поднятом функциональное реактивное программирование (FRP) вызов функции может неявно вызвать создание узла в графе потока данных. Библиотеки реактивного программирования для динамических языков (например, библиотеки Lisp «Cells» и Python «Trellis») могут создавать граф зависимостей на основе анализа значений, считываемых во время выполнения функции, во время выполнения, что позволяет спецификациям потока данных быть как неявными, так и динамическими.

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

Статический или динамический

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

Использование переключателей данных в графе потока данных может в некоторой степени сделать статический граф потока данных динамичным и слегка размыть различия. Однако настоящее динамическое реактивное программирование может использовать императивное программирование для восстановления графа потока данных.

Реактивное программирование высшего порядка

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

Дифференциация потока данных

В идеале все изменения данных распространяются мгновенно, но на практике этого нельзя гарантировать. Вместо этого может потребоваться дать разным частям графа потока данных разные приоритеты оценки. Это можно назвать дифференцированное реактивное программирование.[6]

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

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

Оценочные модели реактивного программирования

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

Было бы проблематично просто наивно распространять изменение с помощью стека из-за потенциальной экспоненциальной сложности обновления, если структура данных имеет определенную форму. Одна такая форма может быть описана как «повторяющаяся форма ромбов» и имеет следующую структуру: Aп→ Bп→ Ап + 1, Ап→ Сп→ Ап + 1, где n = 1,2 ... Эта проблема может быть преодолена путем распространения недействительности только в том случае, если некоторые данные еще не признаны недействительными, а затем повторно проверять данные при необходимости, используя ленивая оценка.

Одна неотъемлемая проблема реактивного программирования состоит в том, что большинство вычислений, которые можно было бы оценить и забыть на нормальном языке программирования, необходимо представить в памяти как структуры данных.[нужна цитата ] Это потенциально может сделать реактивное программирование очень затратным по памяти. Однако исследования того, что называется снижение потенциально может решить эту проблему.[7]

С другой стороны, реактивное программирование - это форма того, что может быть описано как «явный параллелизм».[нужна цитата ], и поэтому может быть полезен для использования мощности параллельного оборудования.

Сходства с паттерном наблюдателя

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

Подходы

Императив

Можно объединить реактивное программирование с обычным императивным программированием. В такой парадигме императивные программы работают с реактивными структурами данных.[8] Такая установка аналогична императивное программирование с ограничениями; однако, в то время как императивное программирование с ограничениями управляет двунаправленными ограничениями, реактивное императивное программирование управляет односторонними ограничениями потока данных.

Объектно-ориентированный

Объектно-ориентированное реактивное программирование (OORP) - это комбинация объектно-ориентированного программирования и реактивного программирования. Пожалуй, наиболее естественный способ создать такую ​​комбинацию: вместо методов и полей объекты имеют реакции которые автоматически переоценивают, когда другие реакции, от которых они зависят, были изменены.[нужна цитата ]

Если язык OORP поддерживает свои императивные методы, он также попадает в категорию императивного реактивного программирования.

Функциональный

Функциональное реактивное программирование (FRP) парадигма программирования для реактивного программирования на функциональное программирование.

На основе правил

Относительно новая категория языков программирования использует ограничения (правила) в качестве основной концепции программирования. Он состоит из реакций на события, которые удовлетворяют все ограничения. Это не только облегчает реакции, основанные на событиях, но и делает реактивные программы инструментом правильности программного обеспечения. Примером языка реактивного программирования, основанного на правилах, является Ampersand, основанный в алгебра отношений.[9]

Смотрите также

  • ReactiveX, API для реализации реактивного программирования с потоками, наблюдаемыми объектами и операторами с множеством языковых реализаций, включая RxJs, RxJava, RxPy и RxSwift.
  • Вяз (язык программирования) Реактивная композиция веб-интерфейса пользователя.
  • Реактивные потоки, стандарт JVM для асинхронной обработки потоков с неблокирующим обратным давлением
  • Наблюдаемый (вычисления), наблюдаемый в реактивном программировании.

Рекомендации

  1. ^ "О Трехгранном". VTScada от Трехгранного. Получено 2020-10-20.
  2. ^ "Язык сценариев SCADA". VTScada от Трехгранного. Получено 2020-10-20.
  3. ^ Решетка, Модель-представление-контроллер и шаблон наблюдателя, Tele сообщество.
  4. ^ «Встраивание динамического потока данных в язык вызова по значению». cs.brown.edu. Получено 2016-10-09.
  5. ^ «Пересечение границ состояний: адаптация объектно-ориентированных фреймворков к функциональным реактивным языкам». cs.brown.edu. Получено 2016-10-09.
  6. ^ «Реактивное программирование - искусство обслуживания | Руководство по управлению ИТ». theartofservice.com. Получено 2016-07-02.
  7. ^ Берчетт, Кимберли; Купер, Грегори Х; Кришнамурти, Шрирам, «Снижение: метод статической оптимизации для прозрачной функциональной реактивности», Материалы симпозиума 2007 ACM SIGPLAN по частичному вычислению и манипулированию программами на основе семантики (PDF), стр. 71–80.
  8. ^ Деметреску, Камил; Финокки, Ирэн; Рибичини, Андреа, "Реактивное императивное программирование с ограничениями потока данных", Материалы международной конференции ACM 2011 по языкам и приложениям объектно-ориентированных систем программирования, стр. 407–26.
  9. ^ Йустен, Стеф (2018 г.), «Алгебра отношений как язык программирования с использованием компилятора Ampersand», Журнал логических и алгебраических методов программирования, 100, стр. 113–29, Дои:10.1016 / j.jlamp.2018.04.002.

внешняя ссылка