Я прочитал статью в Википедии о реактивном программировании . Я также прочитал небольшую статью о функционально-реактивном программировании . Описания довольно абстрактны.
- Что означает функционально-реактивное программирование (FRP) на практике?
- Из чего состоит реактивное программирование (в отличие от нереактивного программирования?)?
Я имею опыт работы с языками императив / OO, так что объяснение, которое относится к этой парадигме, будет оценено.
Ответы:
Если вы хотите почувствовать FRP, вы можете начать со старого урока Fran 1998 года, в котором есть анимированные иллюстрации. Что касается статей, начните с функциональной реактивной анимации, а затем перейдите по ссылкам на ссылку публикаций на моей домашней странице и ссылку FRP на вики Haskell .
Лично мне нравится думать о том, что означает FRP, прежде чем рассматривать, как это может быть реализовано. (Код без спецификации - это ответ без вопросов и, следовательно, «даже не неправильный».) Поэтому я не описываю FRP в терминах представления / реализации, как это делает Томас К. в другом ответе (графики, узлы, ребра, выстрелы, выполнение, так далее). Есть много возможных стилей реализации, но не реализация не говорит , что FRP является .
Я действительно резонирую с простым описанием Лоуренса Г., что FRP о «типах данных, которые представляют значение« с течением времени »». Обычное императивное программирование фиксирует эти динамические значения только косвенно, через состояние и мутации. Полная история (прошлое, настоящее, будущее) не имеет представления первого класса. Кроме того, только (косвенно) могут быть захвачены только дискретно развивающиеся ценности, поскольку императивная парадигма временно дискретна. В отличие от этого, FRP непосредственно фиксирует эти изменяющиеся значения и не имеет проблем с постоянно меняющимися значениями.
FRP также необычен тем, что он происходит одновременно, не сталкиваясь с гнездом теоретических и прагматических крыс, которое изводит императивный параллелизм. Семантически, параллелизм FRP является мелкозернистым , детерминированным и непрерывным . (Я говорю о смысле, а не о реализации. Реализация может включать или не включать параллелизм или параллелизм.) Семантическая определенность очень важна для рассуждений, как строгих, так и неформальных. В то время как параллелизм добавляет огромную сложность императивному программированию (из-за недетерминированного чередования), он не требует усилий в FRP.
Итак, что такое FRP? Вы могли бы изобрести это сами. Начните с этих идей:
Динамические / развивающиеся значения (то есть значения «со временем») сами по себе являются первоклассными значениями. Вы можете определить их и объединить, передать их в функции и из них. Я назвал эти вещи "поведением".
Поведения построены из нескольких примитивов, таких как постоянное (статическое) поведение и время (как часы), а затем с последовательной и параллельной комбинацией. n поведения объединяются путем применения n-арной функции (для статических значений) «по точкам», то есть непрерывно с течением времени.
Для учета дискретных явлений есть другой тип (семейство) «событий», каждое из которых имеет поток (конечный или бесконечный) вхождений. Каждое вхождение имеет ассоциированное время и значение.
Чтобы придумать композиционный словарь, из которого можно построить все виды поведения и события, поиграйте с некоторыми примерами. Продолжайте разбивать на части, которые являются более общими / простыми.
Чтобы вы знали, что находитесь на твердой почве, дайте всей модели композиционную основу, используя технику денотационной семантики, которая просто означает, что (а) каждый тип имеет соответствующий простой и точный математический тип «значений», и ( б) каждый примитив и оператор имеют простое и точное значение в зависимости от значений составляющих. Никогда, никогда не смешивайте соображения реализации с вашим процессом исследования. Если это описание для вас бессмысленно, проконсультируйтесь (а) в Denotational с морфизмами классов типов , (b) двухтактном функциональном реактивном программировании (без учета битов реализации) и (c) на странице викибук Denotational Semantics Haskell, Помните, что денотационная семантика состоит из двух частей, от двух ее основателей Кристофера Стрейчи и Даны Скотт: более простая и полезная часть Стрейчи и более сложная и менее полезная (для разработки программного обеспечения) часть Скотта.
Если вы будете придерживаться этих принципов, я ожидаю, что вы получите что-то более или менее в духе FRP.
Где я взял эти принципы? В разработке программного обеспечения я всегда задаю один и тот же вопрос: «что это значит?». Семантика денотации дала мне точную основу для этого вопроса, которая соответствует моей эстетике (в отличие от операционной или аксиоматической семантики, обе из которых оставляют меня неудовлетворенным). Поэтому я спросил себя, что такое поведение? Вскоре я понял, что дискретно во времени природа императивных вычислений является приспособлением к определенному стилю машины , а не естественным описанием самого поведения. Самое простое точное описание поведения, которое я могу придумать, это просто «функция (непрерывного) времени», так что это моя модель. Восхитительно, эта модель обрабатывает непрерывный, детерминированный параллелизм с легкостью и изяществом.
Реализовать эту модель правильно и эффективно было довольно сложно, но это уже другая история.
источник
В чисто функциональном программировании нет побочных эффектов. Для многих типов программного обеспечения (например, что-либо с взаимодействием с пользователем) побочные эффекты необходимы на некотором уровне.
Один из способов получить побочный эффект, такой как поведение, при сохранении функционального стиля, - это использовать функциональное реактивное программирование. Это сочетание функционального программирования и реактивного программирования. (Статья в Википедии, на которую вы ссылаетесь, посвящена последнему.)
Основная идея реактивного программирования заключается в том, что существуют определенные типы данных, которые представляют значение «со временем». Вычисления, которые включают эти изменяющиеся во времени значения, сами будут иметь значения, которые изменяются со временем.
Например, вы можете представить координаты мыши в виде пары целочисленных значений времени. Допустим, у нас было что-то вроде (это псевдокод):
В любой момент времени x и y будут иметь координаты мыши. В отличие от нереактивного программирования, нам нужно сделать это назначение только один раз, а переменные x и y останутся «актуальными» автоматически. Вот почему реактивное программирование и функциональное программирование так хорошо работают вместе: реактивное программирование устраняет необходимость мутировать переменные, в то же время позволяя вам делать многое из того, что вы могли бы достичь с помощью мутаций переменных.
Если мы затем сделаем некоторые вычисления, основанные на этом, результирующие значения также будут значениями, которые меняются со временем. Например:
В этом примере
minX
всегда будет на 16 меньше, чем координата х указателя мыши. С библиотеками, поддерживающими реагирование, вы можете сказать что-то вроде:А вокруг указателя мыши будет нарисован прямоугольник размером 32x32, который будет отслеживать его перемещение.
Вот довольно хорошая статья о функциональном реактивном программировании .
источник
sqrt(x)
C с вашим макросом, он просто вычислитsqrt(mouse_x())
и вернет мне двойное число. В истинно функциональной реактивной системе,sqrt(x)
вернется новый «двойник со временем». Если бы вы попытались смоделировать FR-систему,#define
вам бы пришлось отказаться от переменных в пользу макросов. Системы FR также, как правило, будут пересчитывать данные только тогда, когда они должны быть пересчитаны, в то время как использование макросов будет означать, что вы будете постоянно переоценивать все, вплоть до подвыражений.Самый простой способ понять, что это такое - представить, что ваша программа - это электронная таблица, а все ваши переменные - это ячейки. Если какая-либо из ячеек в электронной таблице изменится, все ячейки, которые ссылаются на эту ячейку, также изменятся. То же самое с FRP. Теперь представьте, что некоторые ячейки изменяются сами по себе (или, скорее, взяты из внешнего мира): в ситуации с графическим интерфейсом, расположение мыши было бы хорошим примером.
Это обязательно пропускает довольно много. Метафора разрушается довольно быстро, когда вы фактически используете систему FRP. Например, обычно предпринимаются попытки смоделировать отдельные события (например, щелчок мышью). Я просто помещаю это здесь, чтобы дать вам представление о том, на что это похоже.
источник
Для меня это примерно 2 разных значения символа
=
:x = sin(t)
средства, чтоx
это другое название дляsin(t)
. Так что писатьx + y
это тоже самоеsin(t) + y
. Функционально-реактивное программирование в этом отношении похоже на математику: если вы пишетеx + y
, оно вычисляется с тем значением, которое оноt
имеет в момент использования.x = sin(t)
есть присвоение: это означает, чтоx
хранится значение,sin(t)
взятое во время присвоения.источник
x = sin(t)
означаетx
значениеsin(t)
для данногоt
. Это не другое имя дляsin(t)
функции. Иначе это было быx(t) = sin(t)
.2 + 3 = 5
илиa**2 + b**2 = c**2
.Хорошо, из базовых знаний и чтения страницы Википедии, на которую вы указали, кажется, что реактивное программирование - это что-то вроде вычисления потока данных, но с определенными внешними «стимулами», запускающими набор узлов для выполнения и выполнения их вычислений.
Это довольно хорошо подходит для дизайна пользовательского интерфейса, например, когда при касании элемента управления пользовательского интерфейса (скажем, элемента управления громкостью в приложении для воспроизведения музыки) может потребоваться обновить различные элементы отображения и фактическую громкость звука. Когда вы изменяете объем (скажем, слайдер), который соответствует изменению значения, связанного с узлом в ориентированном графе.
Различные узлы, имеющие ребра от этого узла «значение объема», будут автоматически запускаться, и любые необходимые вычисления и обновления будут естественным образом распространяться через приложение. Приложение «реагирует» на пользовательский стимул. Функционально-реактивное программирование было бы просто реализацией этой идеи на функциональном языке или вообще в рамках парадигмы функционального программирования.
Для получения дополнительной информации о «вычислениях потока данных», найдите эти два слова в Википедии или используя вашу любимую поисковую систему. Общая идея такова: программа представляет собой ориентированный граф узлов, каждый из которых выполняет простое вычисление. Эти узлы связаны друг с другом графическими ссылками, которые обеспечивают выходы некоторых узлов на входы других.
Когда узел запускает или выполняет свои вычисления, узлы, подключенные к его выходам, имеют свои соответствующие входы «сработали» или «помечены». Любой узел, имеющий все входы, сработавшие / помеченные / доступные, автоматически срабатывает. График может быть неявным или явным в зависимости от того, как именно реализовано реактивное программирование.
Узлы можно рассматривать как стреляющие параллельно, но часто они выполняются последовательно или с ограниченным параллелизмом (например, их может выполнять несколько потоков). Известным примером была машина потока данных Манчестера , которая (IIRC) использовала архитектуру тегированных данных для планирования выполнения узлов в графе через один или несколько исполнительных блоков. Вычисления потока данных довольно хорошо подходят для ситуаций, в которых запуск вычислений, асинхронно приводящий к каскадам вычислений, работает лучше, чем попытка заставить выполнение управляться часами (или часами).
Реактивное программирование импортирует эту идею «каскада выполнения» и, похоже, думает о программе в виде потока данных, но при условии, что некоторые узлы подключены к «внешнему миру», и каскады выполнения запускаются, когда эти сенсорные -подобные узлы меняются. Выполнение программы тогда будет выглядеть как нечто похожее на сложную рефлекторную дугу. Программа может или не может быть в основном сидячей между стимулами или может установиться в практически сидячем состоянии между стимулами.
«нереактивное» программирование - это программирование с совершенно другим представлением о потоке выполнения и взаимосвязи с внешними входами. Вероятно, это будет несколько субъективно, поскольку люди, скорее всего, будут испытывать искушение сказать что-либо, что реагирует на внешние воздействия, «реагирует» на них. Но если посмотреть на суть дела, программа, которая опрашивает очередь событий с фиксированным интервалом и отправляет любые события, найденные функциям (или потокам), менее реактивна (потому что она обслуживает только ввод данных пользователем с фиксированным интервалом). Опять же, в этом заключается суть: можно представить реализацию опроса с быстрым интервалом опроса в системе на очень низком уровне и программировать реагирующим образом поверх нее.
источник
Прочитав много страниц о FRP, я наконец-то натолкнулся на эту поучительную статью о FRP, которая, наконец, заставила меня понять, что такое FRP на самом деле.
Я цитирую ниже Генриха Апфельма (автора реактивного банана).
Итак, в моем понимании программа FRP представляет собой набор уравнений:
j
дискретно: 1,2,3,4 ...f
зависит от того,t
так что включает в себя возможность моделировать внешние стимулывсе состояние программы заключено в переменные
x_i
Библиотека FRP заботится о прогрессирующем времени, другими словами,
j
оj+1
.Я объясню эти уравнения более подробно в этом видео.
РЕДАКТИРОВАТЬ:
Примерно через 2 года после первоначального ответа, недавно я пришел к выводу, что реализации FRP имеют еще один важный аспект. Они должны (и обычно делают) решить важную практическую проблему: аннулирование кэша .
Уравнения для
x_i
-s описывают граф зависимостей. Когда некоторые изx_i
изменений во времени,j
не все другиеx_i'
значенияj+1
должны быть обновлены, поэтому не все зависимости должны быть пересчитаны, потому что некоторыеx_i'
могут быть независимы отx_i
.Кроме того,
x_i
-s, которые действительно изменяются, могут постепенно обновляться. Для примера рассмотрим операцию по картеf=g.map(_+1)
в Scala, гдеf
иg
находятсяList
вInts
. Здесьf
соответствуетx_i(t_j)
иg
естьx_j(t_j)
. Теперь, если я добавлю элемент кg
этому, было бы расточительно выполнитьmap
операцию для всех элементов вg
. Некоторые реализации FRP (например, reflex-frp ) направлены на решение этой проблемы. Эта проблема также известна как инкрементные вычисления.Другими словами, поведение (
x_i
-ы) в FRP можно рассматривать как вычисления с кэшированием. Задача механизма FRP состоит в том, чтобы эффективно аннулировать и повторно вычислить эти кеш-ы (-ыx_i
), если некоторые изf_i
-s меняются.источник
j+1
". Вместо этого подумайте о функциях непрерывного времени. Как показали нам Ньютон, Лейбниц и другие, часто очень удобно (и «естественно» в буквальном смысле) описывать эти функции по-разному, но непрерывно, используя интегралы и системы ODE. В противном случае вы описываете алгоритм аппроксимации (и плохой) вместо самой вещи.Статья « Просто эффективная функциональная реактивность » Конала Эллиота ( прямой PDF , 233 КБ) является довольно хорошим введением. Соответствующая библиотека также работает.
Теперь этот документ заменен другим документом « Функциональное реактивное программирование Push-pull» ( прямой PDF , 286 КБ).
источник
Отказ от ответственности: мой ответ в контексте rx.js - библиотеки «реактивного программирования» для Javascript.
В функциональном программировании вместо итерации каждого элемента коллекции вы применяете функции более высокого порядка (HoF) к самой коллекции. Таким образом, идея FRP заключается в том, что вместо обработки каждого отдельного события создайте поток событий (реализованный с помощью наблюдаемого *) и примените к нему HoF. Таким образом, вы можете визуализировать систему как конвейеры данных, соединяющие издателей с подписчиками.
Основными преимуществами использования наблюдаемого являются:
i) абстрагирование состояния отсутствия в вашем коде, например, если вы хотите, чтобы обработчик событий запускался только для каждого 'n' -го события, или прекращал срабатывание после первых 'n' событий, или начать стрельбу только после первых 'n' событий, вы можете просто использовать HoFs (filter, takeUntil, skip соответственно) вместо установки, обновления и проверки счетчиков.
ii) это улучшает локальность кода - если у вас есть 5 различных обработчиков событий, изменяющих состояние компонента, вы можете объединить их наблюдаемые и определить вместо них один обработчик событий, объединяя 5 обработчиков событий в один, что делает его очень Легко рассуждать о том, какие события во всей вашей системе могут повлиять на компонент, поскольку все они присутствуют в одном обработчике.
Iterable - это лениво потребляемая последовательность - каждый элемент извлекается итератором всякий раз, когда он хочет его использовать, и, следовательно, перечисление управляется потребителем.
Наблюдаемая - это лениво созданная последовательность - каждый элемент передается наблюдателю всякий раз, когда он добавляется в последовательность, и, следовательно, перечисление управляется производителем.
источник
Чувак, это чертовски блестящая идея! Почему я не узнал об этом еще в 1998 году? Во всяком случае, вот моя интерпретация учебника Fran . Предложения приветствуются, я думаю о запуске игрового движка, основанного на этом.
Вкратце: если каждый компонент можно рассматривать как число, вся система может рассматриваться как математическое уравнение, верно?
источник
Книга Пола Худака « Школа выражений на Хаскеле» - не только хорошее введение в Хаскель, но и достаточно много времени на FRP. Если вы новичок в FRP, я настоятельно рекомендую вам понять, как работает FRP.
Есть также то, что похоже на новое переписывание этой книги (выпущено в 2011 году, обновлено в 2014 году), музыкальная школа Хаскелла .
источник
Согласно предыдущим ответам, кажется, что математически мы просто мыслим в более высоком порядке. Вместо того, чтобы думать о значении x, имеющем тип X , мы думаем о функции x : T → X , где T - тип времени, будь то натуральные числа, целые числа или континуум. Теперь, когда мы пишем y : = x + 1 на языке программирования, мы фактически имеем в виду уравнение y ( t ) = x ( t ) + 1.
источник
Действует как электронная таблица, как отмечено. Обычно основано на фреймворке, управляемом событиями.
Как и во всех «парадигмах», его новизна спорна.
Исходя из моего опыта распределенных потоковых сетей актеров, он может легко стать жертвой общей проблемы согласованности состояний в сети узлов, т. Е. Вы получите много колебаний и попадание в странные циклы.
Этого трудно избежать, поскольку некоторые семантики подразумевают референциальные циклы или широковещание, и могут быть довольно хаотичными, поскольку сеть действующих лиц сходится (или нет) в каком-то непредсказуемом состоянии.
Точно так же некоторые состояния могут быть не достигнуты, несмотря на то, что они имеют четко определенные ребра, поскольку глобальное состояние отклоняется от решения. 2 + 2 может или не может быть 4 в зависимости от того, когда 2 стали 2, и остались ли они таким образом. Таблицы имеют синхронные часы и обнаружение петель. Распределенные актеры обычно не делают.
Все хорошо, весело :).
источник
Я нашел это хорошее видео в субреддите Clojure о FRP. Это довольно легко понять, даже если вы не знаете Clojure.
Вот видео: http://www.youtube.com/watch?v=nket0K1RXU4
Вот источник, на который ссылается видео во второй половине: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs
источник
Эта статья Андре Штальца - лучшее и ясное объяснение, которое я когда-либо видел.
Некоторые цитаты из статьи:
Вот пример фантастических диаграмм, которые являются частью статьи:
источник
Речь идет о математических преобразованиях данных во времени (или игнорировании времени).
В коде это означает функциональную чистоту и декларативное программирование.
Государственные ошибки - огромная проблема в стандартной императивной парадигме. Различные биты кода могут изменять некоторые общие состояния в разное «время» выполнения программ. С этим трудно иметь дело.
В FRP вы описываете (как в декларативном программировании), как данные преобразуются из одного состояния в другое и что его вызывает. Это позволяет вам игнорировать время, потому что ваша функция просто реагирует на свои входные данные и использует их текущие значения для создания нового. Это означает, что состояние содержится в графе (или дереве) узлов преобразования и является функционально чистым.
Это значительно снижает сложность и время отладки.
Подумайте о разнице между A = B + C в математике и A = B + C в программе. В математике вы описываете отношения, которые никогда не изменятся. В программе говорится, что «Прямо сейчас» A - это B + C. Но следующая команда может быть B ++, в этом случае A не равен B + C. В математическом или декларативном программировании A всегда будет равно B + C, независимо от того, в какой момент времени вы спрашиваете.
Таким образом, устраняя сложности общего состояния и изменяя значения с течением времени. Ваша программа гораздо проще рассуждать.
EventStream - это EventStream + некоторая функция преобразования.
Поведение - это EventStream + некоторое значение в памяти.
Когда происходит событие, значение обновляется путем запуска функции преобразования. Получаемое значение сохраняется в памяти поведений.
Поведения могут быть составлены для создания новых моделей поведения, которые трансформируют N других моделей поведения. Это составленное значение будет пересчитано как срабатывание входных событий (поведения).
«Поскольку наблюдатели не имеют состояния, нам часто требуется несколько из них для имитации конечного автомата, как в примере перетаскивания. Мы должны сохранить состояние, в котором он доступен для всех задействованных наблюдателей, например, в переменной path выше».
Цитата из - Устаревший шаблон наблюдателя http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf
источник
Краткое и четкое объяснение о реактивном программировании появляется в Cyclejs - Реактивное программирование , в нем используются простые и наглядные примеры.
Это хорошая точка запуска, а не полный источник знаний. Оттуда вы можете перейти к более сложным и глубоким документам.
источник
Проверьте Rx, Reactive Extensions для .NET. Они указывают, что с IEnumerable вы в основном «вытягиваете» из потока. Запросы Linq по IQueryable / IEnumerable представляют собой операции над множествами, которые «высасывают» результаты из набора. Но с теми же операторами над IObservable вы можете писать запросы Linq, которые «реагируют».
Например, вы можете написать запрос Linq наподобие (из m в MyObservableSetOfMouseMovements, где mX <100 и mY <100 выберите новую точку (mX, mY)).
и с расширениями Rx, вот и все: у вас есть код пользовательского интерфейса, который реагирует на входящий поток движений мыши и рисует, когда вы находитесь в поле 100 100 ...
источник
FRP представляет собой комбинацию функционального программирования (парадигма программирования, основанная на идее, что все является функцией) и парадигмы реактивного программирования (основанная на идее, что все является потоком (философия наблюдателя и наблюдаемого)). Это должно быть лучшим из миров.
Посмотрите на статью Андреа Штальца о реактивном программировании.
источник