Что такое (функциональное) реактивное программирование?

1148

Я прочитал статью в Википедии о реактивном программировании . Я также прочитал небольшую статью о функционально-реактивном программировании . Описания довольно абстрактны.

  1. Что означает функционально-реактивное программирование (FRP) на практике?
  2. Из чего состоит реактивное программирование (в отличие от нереактивного программирования?)?

Я имею опыт работы с языками императив / OO, так что объяснение, которое относится к этой парадигме, будет оценено.

JtR
источник
159
Вот парень с активным воображением и хорошими навыками рассказывания историй. paulstovell.com/reactive-programming
melaos
39
Кому-то действительно нужно написать «Функциональное реактивное программирование для чайников» для всех нас, кто здесь делает автодидакты. Каждый ресурс, который я нашел, даже Элм, похоже, предполагает, что вы получили степень магистра в области CS за последние пять лет. Те, кто знает о FRP, похоже, полностью утратили способность видеть этот вопрос с наивной точки зрения, что является критически важным для преподавания, обучения и евангелизации.
TechZen
26
Еще одно отличное вступление FRP: Введение в Reactive Programming, которое вы пропустили, от моего коллеги Андре
Jonik
5
Один из лучших, которые я видел, Пример на основе: gist.github.com/staltz/868e7e9bc2a7b8c1f754
Размиг
2
Я считаю, что аналогия с таблицами очень полезна в качестве первого грубого впечатления (см. Ответ Боба: stackoverflow.com/a/1033066/1593924 ). Ячейка электронной таблицы реагирует на изменения в других ячейках (тянет), но не протягивает руку и не меняет другие (не подталкивает). Конечным результатом является то, что вы можете изменить одну ячейку, а миллион других «самостоятельно» обновить свои собственные дисплеи.
Джон Кумбс

Ответы:

931

Если вы хотите почувствовать FRP, вы можете начать со старого урока Fran 1998 года, в котором есть анимированные иллюстрации. Что касается статей, начните с функциональной реактивной анимации, а затем перейдите по ссылкам на ссылку публикаций на моей домашней странице и ссылку FRP на вики Haskell .

Лично мне нравится думать о том, что означает FRP, прежде чем рассматривать, как это может быть реализовано. (Код без спецификации - это ответ без вопросов и, следовательно, «даже не неправильный».) Поэтому я не описываю FRP в терминах представления / реализации, как это делает Томас К. в другом ответе (графики, узлы, ребра, выстрелы, выполнение, так далее). Есть много возможных стилей реализации, но не реализация не говорит , что FRP является .

Я действительно резонирую с простым описанием Лоуренса Г., что FRP о «типах данных, которые представляют значение« с течением времени »». Обычное императивное программирование фиксирует эти динамические значения только косвенно, через состояние и мутации. Полная история (прошлое, настоящее, будущее) не имеет представления первого класса. Кроме того, только (косвенно) могут быть захвачены только дискретно развивающиеся ценности, поскольку императивная парадигма временно дискретна. В отличие от этого, FRP непосредственно фиксирует эти изменяющиеся значения и не имеет проблем с постоянно меняющимися значениями.

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

Итак, что такое FRP? Вы могли бы изобрести это сами. Начните с этих идей:

  • Динамические / развивающиеся значения (то есть значения «со временем») сами по себе являются первоклассными значениями. Вы можете определить их и объединить, передать их в функции и из них. Я назвал эти вещи "поведением".

  • Поведения построены из нескольких примитивов, таких как постоянное (статическое) поведение и время (как часы), а затем с последовательной и параллельной комбинацией. n поведения объединяются путем применения n-арной функции (для статических значений) «по точкам», то есть непрерывно с течением времени.

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

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

  • Чтобы вы знали, что находитесь на твердой почве, дайте всей модели композиционную основу, используя технику денотационной семантики, которая просто означает, что (а) каждый тип имеет соответствующий простой и точный математический тип «значений», и ( б) каждый примитив и оператор имеют простое и точное значение в зависимости от значений составляющих. Никогда, никогда не смешивайте соображения реализации с вашим процессом исследования. Если это описание для вас бессмысленно, проконсультируйтесь (а) в Denotational с морфизмами классов типов , (b) двухтактном функциональном реактивном программировании (без учета битов реализации) и (c) на странице викибук Denotational Semantics Haskell, Помните, что денотационная семантика состоит из двух частей, от двух ее основателей Кристофера Стрейчи и Даны Скотт: более простая и полезная часть Стрейчи и более сложная и менее полезная (для разработки программного обеспечения) часть Скотта.

Если вы будете придерживаться этих принципов, я ожидаю, что вы получите что-то более или менее в духе FRP.

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

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

Conal
источник
78
Я знал о функциональном реактивном программировании. Похоже, это связано с моими собственными исследованиями (в области интерактивной статистической графики), и я уверен, что многие из идей будут полезны для моей работы. Однако мне очень трудно обойти язык - должен ли я действительно узнать о «денотационной семантике» и «морфизмах классов типов», чтобы понять, что происходит? Общее ознакомление аудитории с этой темой было бы очень полезно.
Хэдли
212
@Conal: вы четко знаете, о чем говорите, но ваш язык предполагает, что у меня есть докторская степень по вычислительной математике, которой у меня нет. У меня есть опыт работы в области системной инженерии и более чем 20-летний опыт работы с компьютерами и языками программирования, но я все же чувствую, что ваш ответ оставляет меня в замешательстве. Я призываю вас опубликовать свой ответ на английском языке ;-)
mindplay.dk
50
@ minplay.dk: Ваши замечания не дают мне много информации о том, что, в частности, вы не понимаете, и я не склонен делать дикие предположения о том, какое именно подмножество английского языка вы ищете. Тем не менее, я приглашаю вас конкретно сказать, по каким аспектам моего объяснения выше вы столкнулись, чтобы я и другие могли помочь вам. Например, есть ли определенные слова, которые вы хотели бы определить, или понятия, на которые вы хотели бы добавить ссылки? Мне действительно нравится улучшать ясность и доступность моего письма - не останавливая его.
Конал
27
«Определенность» / «определить» означает, что есть единственное, четко определенное правильное значение. Напротив, почти все формы императивного параллелизма могут давать разные ответы, в зависимости от планировщика, от того, ищете вы или нет, и они могут даже зайти в тупик. «Семантический» (и более конкретно «денотационный») относится к значению («денотации») выражения или представления, в отличие от «операционного» (как вычисляется ответ или сколько пространства и / или времени потребляется тем, что вид машины).
Конал
18
Я согласен с @ mindplay.dk, хотя не могу похвастаться тем, что был в поле очень долго. Хотя казалось, что вы знаете, о чем говорите, это не дало мне быстрого, краткого и простого понимания того, что это такое, поскольку я достаточно избалован, чтобы ожидать SO. Этот ответ, в первую очередь, привел меня к множеству новых вопросов, так и не ответив на мой первый. Я надеюсь, что если вы поделитесь своим опытом относительно невежества в этой области, вы поймете, насколько простым и кратким вы действительно должны быть. Я пришел из аналогичного фона, как ОП, кстати.
Аске Б.
739

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

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

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

Например, вы можете представить координаты мыши в виде пары целочисленных значений времени. Допустим, у нас было что-то вроде (это псевдокод):

x = <mouse-x>;
y = <mouse-y>;

В любой момент времени x и y будут иметь координаты мыши. В отличие от нереактивного программирования, нам нужно сделать это назначение только один раз, а переменные x и y останутся «актуальными» автоматически. Вот почему реактивное программирование и функциональное программирование так хорошо работают вместе: реактивное программирование устраняет необходимость мутировать переменные, в то же время позволяя вам делать многое из того, что вы могли бы достичь с помощью мутаций переменных.

Если мы затем сделаем некоторые вычисления, основанные на этом, результирующие значения также будут значениями, которые меняются со временем. Например:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

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

rectangle(minX, minY, maxX, maxY)

А вокруг указателя мыши будет нарисован прямоугольник размером 32x32, который будет отслеживать его перемещение.

Вот довольно хорошая статья о функциональном реактивном программировании .

Лоуренс Гонсалвес
источник
25
То есть реактивное программирование является формой декларативного программирования?
troelskn
31
> То есть реактивное программирование является формой декларативного программирования? Функциональное реактивное программирование - это форма функционального программирования, которая является формой декларативного программирования.
Конал
7
@ user712092 Не совсем, нет. Например, если я вызову sqrt(x)C с вашим макросом, он просто вычислит sqrt(mouse_x())и вернет мне двойное число. В истинно функциональной реактивной системе, sqrt(x)вернется новый «двойник со временем». Если бы вы попытались смоделировать FR-систему, #defineвам бы пришлось отказаться от переменных в пользу макросов. Системы FR также, как правило, будут пересчитывать данные только тогда, когда они должны быть пересчитаны, в то время как использование макросов будет означать, что вы будете постоянно переоценивать все, вплоть до подвыражений.
Лоуренс Гонсалвес
4
«Для многих типов программного обеспечения (например, что-либо с взаимодействием с пользователем) побочные эффекты необходимы на некотором уровне». И, возможно, только на уровне реализации. В реализации чистого, ленивого функционального программирования есть много побочных эффектов, и один из успехов этой парадигмы состоит в том, чтобы исключить многие из этих эффектов из модели программирования. Мои собственные набеги на функциональные пользовательские интерфейсы предполагают, что они также могут быть запрограммированы полностью без побочных эффектов.
Конал
4
@tieTYT x никогда не переназначается / не изменяется. Значение х - это последовательность значений во времени. Другой способ взглянуть на это состоит в том, что вместо x, имеющего «нормальное» значение, такое как число, значение x (концептуально) является функцией, для которой в качестве параметра требуется время. (Это немного упрощает. Вы не можете создавать значения времени, которые позволили бы вам предсказывать будущее таких вещей, как положение мыши.)
Лоуренс Гонсалвес
144

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

Это обязательно пропускает довольно много. Метафора разрушается довольно быстро, когда вы фактически используете систему FRP. Например, обычно предпринимаются попытки смоделировать отдельные события (например, щелчок мышью). Я просто помещаю это здесь, чтобы дать вам представление о том, на что это похоже.


источник
3
Чрезвычайно подходящий пример. Здорово иметь теоретические вещи, и, возможно, некоторые люди понимают последствия этого, не прибегая к основательному примеру, но мне нужно начать с того, что это делает для меня, а не с того, что это абстрактно. Что я только недавно получил (из бесед Rx от Netflix!), Так это то, что RP (или Rx, в любом случае) делает эти «изменяющиеся значения» первоклассными и позволяет вам рассуждать о них или писать функции, которые с ними что-то делают. Напишите функции для создания электронных таблиц или ячеек, если хотите. И он обрабатывает, когда значение заканчивается (уходит) и позволяет вам очистить автоматически.
Бенджон
В этом примере подчеркивается разница между программированием на основе событий и реактивным подходом, когда вы просто объявляете зависимости для использования интеллектуальной маршрутизации.
Кинжелом
131

Для меня это примерно 2 разных значения символа =:

  1. В математике x = sin(t)средства, что xэто другое название для sin(t). Так что писать x + yэто тоже самое sin(t) + y. Функционально-реактивное программирование в этом отношении похоже на математику: если вы пишете x + y, оно вычисляется с тем значением, которое оно tимеет в момент использования.
  2. В C-подобных языках программирования (императивных языках) x = sin(t)есть присвоение: это означает, что xхранится значение, sin(t) взятое во время присвоения.
user712092
источник
5
Хорошее объяснение. Я думаю, вы могли бы также добавить, что «время» в смысле FRP - это обычно «любое изменение от внешнего ввода». Каждый раз, когда внешняя сила изменяет ввод FRP, вы перемещаете «время» вперед и снова пересчитываете все, что затронуто изменением.
Дидье А.
4
В математике x = sin(t)означает xзначение sin(t)для данного t. Это не другое имя для sin(t)функции. Иначе это было бы x(t) = sin(t).
Дмитрий Зайцев
+ Дмитрий Зайцев Знак равенства имеет несколько значений в математике. Одним из них является то, что всякий раз, когда Вы видите левую сторону, Вы можете поменять его правой стороной. Например 2 + 3 = 5или a**2 + b**2 = c**2.
user712092 10.11.16
71

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

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

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

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

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

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

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

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

Томас Каммейер
источник
1
Хорошо, сейчас есть несколько хороших ответов. Должен ли я удалить свой пост? Если я увижу, что два или три человека говорят, что это ничего не добавляет, я удалю его, если количество полезных сообщений не увеличится. Нет смысла оставлять это здесь, если это не добавляет что-то ценное.
Томас Каммейер
3
Вы упомянули поток данных, так что добавьте какое-то значение IMHO.
Райнер Йосвиг
Кажется, так и должно быть QML;)
mlvljr
3
Для меня этот ответ был наиболее легким для понимания, особенно потому, что использовались естественные аналоги, такие как «рябь в приложении» и «сенсорные узлы». Большой!
Аксели Пален
1
к сожалению, ссылка на Manchester Dataflow Machine не работает.
Pac0
65

Прочитав много страниц о FRP, я наконец-то натолкнулся на эту поучительную статью о FRP, которая, наконец, заставила меня понять, что такое FRP на самом деле.

Я цитирую ниже Генриха Апфельма (автора реактивного банана).

В чем суть функционально-реактивного программирования?

Распространенным ответом будет то, что «FRP - это все, что описывает систему в терминах изменяющихся во времени функций вместо изменяемого состояния», и это, безусловно, не будет ошибкой. Это семантическая точка зрения. Но, на мой взгляд, более глубокий, более удовлетворительный ответ дает следующий чисто синтаксический критерий:

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

Например, возьмем пример счетчика: у вас есть две кнопки с метками «Вверх» и «Вниз», которые можно использовать для увеличения или уменьшения счетчика. Обязательно, вы должны сначала указать начальное значение, а затем изменить его при каждом нажатии кнопки; что-то вроде этого:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

Дело в том, что на момент объявления указывается только начальное значение счетчика; динамическое поведение счетчика подразумевается в остальной части текста программы. Напротив, функциональное реактивное программирование задает все динамическое поведение во время объявления, как это:

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)

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

Итак, в моем понимании программа 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 меняются.

jhegedus
источник
4
Я был там с вами, пока вы не пошли с дискретными уравнениями. Основополагающей идеей FRP было непрерывное время , где нет " j+1". Вместо этого подумайте о функциях непрерывного времени. Как показали нам Ньютон, Лейбниц и другие, часто очень удобно (и «естественно» в буквальном смысле) описывать эти функции по-разному, но непрерывно, используя интегралы и системы ODE. В противном случае вы описываете алгоритм аппроксимации (и плохой) вместо самой вещи.
Конал
Кажется, что язык layx для HTML-шаблонов и ограничений макета выражает элементы FRP.
@Conal это заставляет меня задуматься, чем FRP отличается от ODE. Чем они отличаются?
Джегедус
@jhegedus В этой интеграции (возможно, рекурсивной, т. е. ODE) предоставляется один из строительных блоков FRP, а не весь. Каждый элемент словаря FRP (включая, но не ограничиваясь интеграцией) точно объясняется в терминах непрерывного времени. Это объяснение помогает?
Конал
29

Статья « Просто эффективная функциональная реактивность » Конала Эллиота ( прямой PDF , 233 КБ) является довольно хорошим введением. Соответствующая библиотека также работает.

Теперь этот документ заменен другим документом « Функциональное реактивное программирование Push-pull» ( прямой PDF , 286 КБ).

scvalex
источник
29

Отказ от ответственности: мой ответ в контексте rx.js - библиотеки «реактивного программирования» для Javascript.

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

Основными преимуществами использования наблюдаемого являются:
i) абстрагирование состояния отсутствия в вашем коде, например, если вы хотите, чтобы обработчик событий запускался только для каждого 'n' -го события, или прекращал срабатывание после первых 'n' событий, или начать стрельбу только после первых 'n' событий, вы можете просто использовать HoFs (filter, takeUntil, skip соответственно) вместо установки, обновления и проверки счетчиков.
ii) это улучшает локальность кода - если у вас есть 5 различных обработчиков событий, изменяющих состояние компонента, вы можете объединить их наблюдаемые и определить вместо них один обработчик событий, объединяя 5 обработчиков событий в один, что делает его очень Легко рассуждать о том, какие события во всей вашей системе могут повлиять на компонент, поскольку все они присутствуют в одном обработчике.

  • Observable - это двойственное от Iterable.

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

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

tldr
источник
1
Большое спасибо за это прямое определение наблюдаемой и ее отличия от итерируемых. Я думаю, что часто очень полезно сравнивать сложную концепцию с ее хорошо известной двойной концепцией, чтобы получить истинное понимание.
2
«Таким образом, идея FRP заключается в том, что вместо обработки каждого отдельного события создайте поток событий (реализованный с помощью наблюдаемого *) и примените к нему HoF». Я могу ошибаться, но я считаю, что это на самом деле не FRP, а скорее хорошая абстракция над шаблоном проектирования Observer, который позволяет выполнять функциональные операции через HoF (что здорово!), Хотя по-прежнему предназначен для использования с императивным кодом. Обсуждение по теме - lambda-the-ultimate.org/node/4982
nqe
18

Чувак, это чертовски блестящая идея! Почему я не узнал об этом еще в 1998 году? Во всяком случае, вот моя интерпретация учебника Fran . Предложения приветствуются, я думаю о запуске игрового движка, основанного на этом.

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

Вкратце: если каждый компонент можно рассматривать как число, вся система может рассматриваться как математическое уравнение, верно?

Дэн Росс
источник
1
Это немного поздно, но в любом случае ... Frag - игра, использующая FRP .
arx
14

Книга Пола Худака « Школа выражений на Хаскеле» - не только хорошее введение в Хаскель, но и достаточно много времени на FRP. Если вы новичок в FRP, я настоятельно рекомендую вам понять, как работает FRP.

Есть также то, что похоже на новое переписывание этой книги (выпущено в 2011 году, обновлено в 2014 году), музыкальная школа Хаскелла .

CJS
источник
10

Согласно предыдущим ответам, кажется, что математически мы просто мыслим в более высоком порядке. Вместо того, чтобы думать о значении x, имеющем тип X , мы думаем о функции x : TX , где T - тип времени, будь то натуральные числа, целые числа или континуум. Теперь, когда мы пишем y : = x + 1 на языке программирования, мы фактически имеем в виду уравнение y ( t ) = x ( t ) + 1.

Yuning
источник
9

Действует как электронная таблица, как отмечено. Обычно основано на фреймворке, управляемом событиями.

Как и во всех «парадигмах», его новизна спорна.

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

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

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

Все хорошо, весело :).

emperorz
источник
8

Я нашел это хорошее видео в субреддите Clojure о FRP. Это довольно легко понять, даже если вы не знаете Clojure.

Вот видео: http://www.youtube.com/watch?v=nket0K1RXU4

Вот источник, на который ссылается видео во второй половине: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs

Даниэль Каплан
источник
7

Эта статья Андре Штальца - лучшее и ясное объяснение, которое я когда-либо видел.

Некоторые цитаты из статьи:

Реактивное программирование - это программирование с асинхронными потоками данных.

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

Вот пример фантастических диаграмм, которые являются частью статьи:

Нажмите на диаграмму потока событий

GreenGiant
источник
5

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

В коде это означает функциональную чистоту и декларативное программирование.

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

В 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

Джей Шеферд
источник
Именно так я отношусь к декларативному программированию, и вы просто описываете эту идею лучше меня.
neevek
2

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

[Модуль / Компонент / объект] является реактивным, то есть он полностью отвечает за управление своим состоянием, реагируя на внешние события.

В чем выгода этого подхода? Это Инверсия Контроля , главным образом потому, что [модуль / Компонент / объект] отвечает за себя, улучшая инкапсуляцию, используя частные методы по сравнению с публичными.

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

pdorgambide
источник
0

Проверьте Rx, Reactive Extensions для .NET. Они указывают, что с IEnumerable вы в основном «вытягиваете» из потока. Запросы Linq по IQueryable / IEnumerable представляют собой операции над множествами, которые «высасывают» результаты из набора. Но с теми же операторами над IObservable вы можете писать запросы Linq, которые «реагируют».

Например, вы можете написать запрос Linq наподобие (из m в MyObservableSetOfMouseMovements, где mX <100 и mY <100 выберите новую точку (mX, mY)).

и с расширениями Rx, вот и все: у вас есть код пользовательского интерфейса, который реагирует на входящий поток движений мыши и рисует, когда вы находитесь в поле 100 100 ...

часовой
источник
0

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

Посмотрите на статью Андреа Штальца о реактивном программировании.

Кришна Ганеривал
источник