stringtranslate.com

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

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

Например, в императивном программировании это a := b + cбудет означать, что aприсваивается результат b + cв момент вычисления выражения, а позже значения bи cмогут быть изменены без какого-либо влияния на значение a. С другой стороны, в реактивном программировании значение aавтоматически обновляется всякий раз, когда изменяются значения bили c, без необходимости для программы явно переформулировать оператор a := b + cдля повторного назначения значения a. [ необходима цитата ]

var b = 1 var c = 2 var a = b + c b = 10 console . log ( a ) // 3 (не 12, потому что "=" не является реактивным оператором присваивания)              // теперь представьте, что у вас есть специальный оператор "$=", который изменяет значение переменной (выполняет код справа от оператора и присваивает результат переменной слева) не только при явной инициализации, но и при изменении ссылочных переменных (справа от оператора) var b = 1 var c = 2 var a $ = b + c b = 10 console . log ( a ) // 12              

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

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

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

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

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

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

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

Методы внедрения и проблемы

Суть внедрения

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

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

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

Что толкать?

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

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

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

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

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

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

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

Глюки

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

t = секунды + 1г = (t > секунд)

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

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

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

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

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

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

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

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

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

т = если ((секунды mod 2) == 0): секунды + 1 еще: секунд - 1 конецт + 1

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

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

Концепции

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подходы

Императив

Можно объединить реактивное программирование с обычным императивным программированием . В такой парадигме императивные программы работают с реактивными структурами данных. [5] Такая настройка аналогична императивному программированию с ограничениями ; однако, в то время как императивное программирование с ограничениями управляет двунаправленными ограничениями потока данных, императивное реактивное программирование управляет односторонними ограничениями потока данных. Одной из эталонных реализаций является предлагаемое расширение среды выполнения Quantum для JavaScript.

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

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

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

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

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

Актерский состав

Для разработки реактивных систем были предложены акторы, часто в сочетании с функциональным реактивным программированием (FRP) и реактивными потоками для разработки распределенных реактивных систем. [6] [7] [8] [9]

Основанный на правилах

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

Реализации

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

Ссылки

  1. ^ Trellis, Model-view-controller и шаблон observer, сообщество Tele, заархивировано из оригинала 2016-03-03 , извлечено 2009-07-27.
  2. ^ "Внедрение динамического потока данных в язык вызова по значению". cs.brown.edu . Архивировано из оригинала 2016-10-18 . Получено 2016-10-09 .
  3. ^ "Crossing State Lines: Adapting Object-Oriented Frameworks to Functional Reactive Languages". cs.brown.edu . Архивировано из оригинала 2016-10-09 . Получено 2016-10-09 .
  4. ^ Берчетт, Кимберли; Купер, Грегори Х; Кришнамурти, Шрирам, «Понижение: метод статической оптимизации для прозрачной функциональной реактивности», Труды симпозиума ACM SIGPLAN 2007 года по частичной оценке и манипулированию программами на основе семантики (PDF) , стр. 71–80, архивировано (PDF) из оригинала 16.04.2016 , извлечено 08.09.2014.
  5. ^ Деметреску, Камил; Финокки, Ирен; Рибикини, Андреа (22 октября 2011 г.), «Реактивное императивное программирование с ограничениями потока данных», Труды международной конференции ACM 2011 г. по языкам и приложениям объектно-ориентированных систем программирования, Oopsla '11, стр. 407–26, arXiv : 1104.2293 , doi :10.1145/2048066.2048100, ISBN 9781450309400, S2CID  7285961.
  6. ^ Ван ден Вондер, Сэм; Рено, Тьерри; Ойен, Бьярно; Де Костер, Джоэри; Де Мейтер, Вольфганг (2020), «Борьба с неуклюжим отрядом для реактивного программирования: модель «актор-реактор»», Международные труды Лейбница по информатике (LIPIcs), т. 166, стр. 19:1–19:29, doi : 10.4230/LIPIcs.ECOOP.2020.19 , ISBN 9783959771542, S2CID  260445152, заархивировано из оригинала 20.10.2021 , извлечено 24.06.2021.
  7. ^ Ван ден Вондер, Сэм; Рено, Тьерри; Де Мейтер, Вольфганг (2022), «Реактивность на уровне топологии в распределенных реактивных программах: управление реактивным знакомством с использованием стай», Искусство, наука и инженерия программирования, т. 6, стр. 14:1–14:36, arXiv : 2202.09228 , doi : 10.22152/programming-journal.org/2022/6/14 , заархивировано из оригинала 2023-03-15 , извлечено 2023-03-16
  8. ^ Шибанаи, Казухиро; Ватанабэ, Такуо (2018), «Распределенное функциональное реактивное программирование в среде выполнения на основе акторов», Труды 8-го международного семинара ACM SIGPLAN по программированию на основе акторов, агентов и децентрализованного управления, Agere 2018, стр. 13–22, doi : 10.1145/3281366.3281370, ISBN 9781450360661, S2CID  53113447, заархивировано из оригинала 2021-06-24 , извлечено 2021-06-24
  9. ^ Ростенбург, Раймонд; Баккер, Роб; Уильямс, Роб (2016). «13: Потоковое вещание». Акка в действии . Гринвич, Коннектикут, США: Manning Publications Co. 281. ИСБН 978-1-61729-101-2.
  10. ^ Йостен, Стеф (2018), «Реляционная алгебра как язык программирования с использованием компилятора Ampersand», Журнал логических и алгебраических методов в программировании , т. 100, стр. 113–29, doi : 10.1016/j.jlamp.2018.04.002 , S2CID  52932824.

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