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

редактировать

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

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

Например, в настройке императивного программирования a: = b + c {\ displaystyle a: = b + c}a: = b + c будет означать, что a { \ displaystyle a}a присваивается результат b + c {\ displaystyle b + c}b + c в момент вычисления выражения, а затем значения b {\ displaystyle b}bи c {\ displaystyle c}c можно изменить без влияния на значение a {\ displaystyle a}a . С другой стороны, при реактивном программировании значение a {\ displaystyle a}a автоматически обновляется всякий раз, когда значения b {\ displaystyle b}bили c {\ displaystyle c}c изменение без необходимости повторного выполнения инструкции программе a: = b + c {\ displaystyle a: = b + c}a: = b + c для определения присвоенного в настоящее время значения a. {\ displaystyle a.}a.

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

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

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

Содержание

  • 1 Подходы к созданию языков реактивного программирования
  • 2 Модели и семантика программирования
  • 3 Методы реализации и проблемы
    • 3.1 Суть реализаций
      • 3.1.1 Алгоритмы распространения изменений
      • 3.1.2 Что подтолкнуть?
    • 3.2 Проблемы реализации в реактивном программировании
      • 3.2.1 Сбои
      • 3.2.2 Циклические зависимости
      • 3.2.3 Взаимодействие с изменяемым состоянием
      • 3.2.4 Динамическое обновление графика зависимостей
  • 4 Концепции
    • 4.1 Степени ясности
    • 4.2 Статическое или динамическое
    • 4.3 Реактивное программирование высшего порядка
    • 4.4 Дифференциация потоков данных
    • 4.5 Модели оценки реактивного программирования
      • 4.5.1 Сходства с шаблоном наблюдателя
  • 5 Подходы
    • 5.1 Императив
    • 5.2 Объектно-ориентированный
    • 5.3 Функциональный
    • 5.4 Основанный на правилах
  • 6 См. Также
  • 7 Ссылки
  • 8 Внешние ссылки

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

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

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

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

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

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

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

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

Алгоритмы распространения изменений

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

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

Что отправлять?

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

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

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

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

Существует два основных способа построения графа зависимостей:

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

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

Сбои

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Концепции

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подходы

Императив

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

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

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

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

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

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

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

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

См. Также

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

Ссылки

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

Внешние ссылки

Последняя правка сделана 2021-06-03 09:47:57
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте