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


1148

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

  1. Що означає функціональне реактивне програмування (FRP) на практиці?
  2. З чого складається реактивне програмування (на відміну від нереактивного програмування?)?

Моя інформація складається з імперативних мов / OO, тому пояснення, яке стосується цієї парадигми, було б вдячно.


159
ось хлопець з активною уявою та гарними навичками розповіді бере на себе всю справу. paulstovell.com/reactive-programming
melaos

39
Комусь справді потрібно написати "Функціональне реактивне програмування для манекенів" для всіх нас, які автодидактируються тут. Кожен ресурс, який я знайшов, навіть Elm, здається, ви здобули ступінь магістра в КС за останні п’ять років. Ті, хто знає про FRP, здається, повністю втратили здатність бачити справу з наївного погляду, що є критичним для навчання, навчання та євангелізації.
TechZen


5
Один з найкращих, що я бачив, на прикладі: gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig

2
Я вважаю аналогію електронної таблиці дуже корисною як перше грубе враження (див. Відповідь Боба: stackoverflow.com/a/1033066/1593924 ). Клітина електронної таблиці реагує на зміни в інших клітинах (тягне), але не охоплює та не змінює інших (не натискає). Кінцевий результат полягає в тому, що ви можете змінити одну клітинку і мільйон інших «незалежно» оновити власні дисплеї.
Джон Кумбс

Відповіді:


931

Якщо ви хочете відчути FRP, ви можете почати зі старого підручника з Фран з 1998 року, який містить анімовані ілюстрації. Для паперів почніть з функціональної реактивної анімації, а потім перейдіть за посиланнями на посилання публікацій на моїй домашній сторінці та посиланням FRP на вікі Haskell .

Особисто мені подобається подумати над тим, що означає FRP, перш ніж звертатися до того, як це може бути реалізовано. (Код без специфікації - це відповідь без запитання, і, отже, «навіть не помиляється».) Тому я не описую FRP в умовах представлення / реалізації, як це робить Thomas K в іншій відповіді (графіки, вузли, ребра, стрільба, виконання, тощо). Є багато можливих стилів реалізації, але не реалізація не говорить , що FRP є .

Я резоную з простим описом Лоранса Г, що FRP - це "типи даних, які представляють значення" з часом ". Звичайне імперативне програмування фіксує ці динамічні значення лише опосередковано, через стан та мутації. Повна історія (минула, теперішня, майбутня) не має представлення першого класу. Більше того, можна (побічно) захоплювати лише дискретно розвиваються значення, оскільки імперативна парадигма тимчасово дискретна. На відміну від цього, FRP фіксує ці зміни, що змінюються, безпосередньо і не має труднощів з постійно змінюються значеннями.

FRP також незвичний тим, що він є одночасною, не стикаючись з теоретичним і прагматичним гніздом щурів, що наносить імперативну сукупність. Семантично паралельність FRP є дрібнозернистою , визначеною та безперервною . (Я говорю про значення, а не про імплементацію. Впровадження може включати паралелізм чи паралелізм.) Семантична рішучість є дуже важливою для міркувань, як суворих, так і неформальних. У той час як паралельність додає величезної складності імперативному програмуванню (через недетерміновану переплетення), це є без зусиль у FRP.

Отже, що таке FRP? Ви могли це самі придумати. Почніть з цих ідей:

  • Динамічні / що розвиваються значення (тобто значення "з часом") самі по собі є першокласними значеннями. Ви можете їх визначити та комбінувати, передати їх у функції та вийти з них. Я назвав ці речі "поведінкою".

  • Поведінки будуються з декількох примітивів, таких як постійні (статичні) поведінки та час (як годинник), а потім із послідовним та паралельним поєднанням. n поведінка поєднується за допомогою застосування n-арної функції (на статичні значення), "точково", тобто постійно в часі.

  • Для обліку дискретних явищ є інший тип (сімейство) "подій", кожен з яких має потік (скінченний або нескінченний) подій. Кожне виникнення має пов'язаний час та значення.

  • Щоб придумати композиційну лексику, з якої можна побудувати всі поведінки та події, пограйте з деякими прикладами. Продовжуйте деконструювати деталі, які є більш загальними / простими.

  • Так що ви знаєте, що ви перебуваєте на твердій основі, дайте всій моделі композиційну основу, використовуючи техніку денотаційної семантики, що просто означає, що (а) кожен тип має відповідний простий і точний математичний тип "смислів", і ( б) кожен примітив і оператор має просте і точне значення як функцію значень складових. Ніколи, ніколи не змішуйте міркування щодо впровадження у процесі дослідження. Якщо цей опис для вас химерний, зверніться до: (а) Денотаційного дизайну з типом морфізмів класів , (б) функціонального реактивного програмування "Push-pull" (ігнорування бітів реалізації) та (c) сторінки вікі-книг " Денотаційна семантика Haskell".. Будьте уважні, що денотаційна семантика має дві частини, від двох її засновників Крістофера Страчі та Дани Скотта: простіша і корисніша частина Страчі та більш тверда і менш корисна (для розробки програмного забезпечення) частина Скотта.

Якщо ви будете дотримуватися цих принципів, я думаю, ви отримаєте щось більш-менш у дусі FRP.

Звідки я взяв ці принципи? У розробці програмного забезпечення я завжди задаю одне і те ж питання: "що це означає?". Денотаційна семантика дала мені точну основу для цього питання, і та, яка відповідає моїй естетиці (на відміну від оперативної чи аксіоматичної семантики, обидві не залишають мене незадоволеними). Тож я запитав себе, що таке поведінка? Я незабаром зрозумів, що тимчасово дискретний характер імперативних обчислень - це пристосування до певного стилю машини , а не природний опис самої поведінки. Найпростіший точний опис поведінки, про який я можу придумати, - це просто "функція (безперервного) часу", тож це моя модель. Ця модель чудово справляється з безперервною, детермінованою одночасністю з легкістю та витонченістю.

Впровадити цю модель правильно та ефективно було вже досить складно, але це вже інша історія.


78
Мені відомо про функціональне реактивне програмування. Це здається пов'язаним з моїм власним дослідженням (в інтерактивній статистичній графіці), і я впевнений, що багато ідей будуть корисні для моєї роботи. Однак мені буває дуже важко обійти мову - чи справді я повинен дізнатися про "денотаційну семантику" та "тип морфізмів класу", щоб зрозуміти, що відбувається? Загальне ознайомлення аудиторії з цією темою було б дуже корисним.
hadley

212
@Conal: ви чітко знаєте, про що ви говорите, але ваша мова передбачає, що я маю доктор з обчислювальної математики, чого я не роблю. У мене є досвід в галузі системної інженерії та 20-річний досвід роботи з комп'ютерами та мовами програмування, я все ще відчуваю, що ваша відповідь залишає мене збентеженою. Я закликаю вас перекласти свою відповідь англійською мовою ;-)
mindplay.dk

50
@ minplay.dk: Ваші зауваження не дають мені великої уваги щодо того, що, зокрема, ви не розумієте, і я не хочу робити здогадів про те, яку саме підмножину англійської мови ви шукаєте. Однак я закликаю вас сказати конкретно, які аспекти мого пояснення вище ви маєте відключити, щоб я та інші могли вам допомогти. Наприклад, чи є конкретні слова, які ви хочете визначити, або поняття, для яких ви хочете додати посилання? Мені дуже подобається вдосконалювати ясність та доступність моєї писемності - не скидаючи її нанівець.
Конал

27
"Рішучість" / "визначити" означає, що існує єдине, чітко визначене правильне значення. Навпаки, майже всі форми імперативної одночасності можуть давати різні відповіді, залежно від планувальника чи ви шукаєте чи ні, і вони можуть навіть тупик. "Семантичний" (а точніше "денотаційний") відноситься до значення ("позначення") виразу або представлення, на відміну від "оперативного" (як обчислюється відповідь або скільки споживається місця та / або часу на що вид машини).
Конал

18
Я погоджуюся з @ mindplay.dk, хоча не можу похвалитися тим, що був у цій галузі дуже довго. Хоча здавалося, що ви знаєте, про що говорите, це не дало мені швидкого, короткого та простого розуміння того, що це таке, оскільки я досить розпещений, щоб очікувати на SO. Ця відповідь в першу чергу привела мене до тонни нових запитань, не відповідаючи на моє перше. Я сподіваюся, що обмін досвідом щодо того, що в цій галузі залишаються відносно неосвіченими, може дати вам уявлення про те, наскільки просто та коротко вам потрібно бути. Я походжу з аналогічного фону, як OP, btw.
Aske B.

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 менше, ніж координата x вказівника миші. З бібліотеками, що знають про реактивність, ви можете сказати щось на кшталт:

rectangle(minX, minY, maxX, maxY)

А навколо вказівника миші буде намальовано вікно розміром 32х32 і буде відстежувати його, куди він рухається.

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


25
Тож реактивне програмування є формою декларативного програмування тоді?
troelskn

31
> Тож реактивне програмування є формою декларативного програмування? Функціональне реактивне програмування - це форма функціонального програмування, яка є формою декларативного програмування.
Конал

7
@ user712092 Не дуже, ні. Наприклад, якщо я дзвоню sqrt(x)в C зі своїм макросом, він просто обчислює sqrt(mouse_x())і повертає мені подвійний. У справжній функціональній реактивній системі sqrt(x)повернувся би новий "подвійний з часом". Якби ви намагалися змоделювати систему FR з #defineвами, вам доведеться, мабуть, присягнути змінні на користь макросів. FR-системи також зазвичай перераховують лише ті речі, коли її потрібно перерахувати, тоді як використання макросів означатиме, що ви будете постійно переоцінювати все, аж до субекспресій.
Лоранс Гонсалвс

4
"Для багатьох типів програмного забезпечення (наприклад, будь-що з взаємодією з користувачем) побічні ефекти необхідні на певному рівні." І, можливо, лише на рівні реалізації. Існує багато побічних ефектів при здійсненні чистого, лінивого функціонального програмування, і один з успіхів парадигми - це утримати багато таких ефектів поза моделлю програмування. Мої власні перегляди функціональних інтерфейсів користувача дозволяють запрограмувати цілком без побічних ефектів.
Конал

4
@tieTYT x ніколи не призначається та не змінюється. значення x - це послідовність значень у часі. Інший спосіб поглянути на це, що замість того, щоб x має "нормальне" значення, як число, значення x - це (концептуально) функція, яка займає час як параметр. (Це трохи надмірне спрощення. Ви не можете створити значення часу, які б дозволили передбачити майбутнє таких речей, як положення миші.)
Лоранс Гонсальвс,

144

Простий спосіб досягти першої інтуїції щодо того, що це таке - уявити свою програму - це електронна таблиця, і всі ваші змінні є клітинками. Якщо будь-яка з комірок електронної таблиці зміниться, будь-які комірки, які посилаються на цю клітинку, також змінюються. Так само і з FRP. Тепер уявіть, що деякі клітини змінюються самостійно (вірніше, взяті із зовнішнього світу): у ситуації з графічним інтерфейсом позиція миші може бути хорошим прикладом.

Це обов'язково пропускає досить багато. Метафора руйнується досить швидко, коли ви фактично використовуєте систему FRP. Для одного, як правило, також намагаються моделювати дискретні події (наприклад, клацання миші). Я лише ставлю це сюди, щоб дати вам уявлення, що це таке.


3
Надзвичайно помітний приклад. Чудово мати теоретичні речі, і, можливо, деякі люди отримують наслідки цього, не звертаючись до обґрунтованого прикладу, але мені потрібно почати з того, що це робить для мене, а не з того, що це абстрактно. Що я нещодавно отримав (із розмов про Rx від Netflix!) - це те, що RP (або Rx, у будь-якому випадку) робить ці "змінні значення" першокласними і дозволяє вам міркувати про них, або записувати функції, які роблять з ними справи. Запишіть функції для створення електронних таблиць або комірок, якщо вам це подобається. І він обробляє, коли значення закінчується (відходить) і дозволяє автоматично очищати.
Benjohn

Цей приклад підкреслює різницю між керованим подіями програмуванням та реактивним підходом, де ви просто оголошуєте залежності для використання інтелектуальної маршрутизації.
кіньєлом

131

Для мене це приблизно два різних значення символу =:

  1. В математиці x = sin(t)кошти, що xце інша назва для sin(t). Отже, писати x + y- це те саме, що і sin(t) + y. У цьому відношенні функціональне реактивне програмування схоже на математику: якщо ви пишете x + y, воно обчислюється залежно від того, яке значення tмає в той момент, коли воно використовується.
  2. У C-подібних мовах програмування (імперативні мови) x = sin(t)є призначенням: це означає, що xзберігає значення, sin(t) взяті на момент виконання завдання.

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.
користувач712092

71

Гаразд, з фонових знань та з читання сторінки Вікіпедії, на яку ви вказали, виявляється, що реактивне програмування - це щось на зразок обчислень потоку даних, але зі специфічними зовнішніми "стимулами", що викликають набір вузлів для запуску та проведення їх обчислень.

Це дуже добре підходить для дизайну інтерфейсу, наприклад, коли для торкання керування користувальницьким інтерфейсом (скажімо, регулювання гучності програми для відтворення музики) може знадобитися оновлення різних елементів відображення та фактичного обсягу аудіовиходу. Коли ви змінюєте гучність (слайдер, скажімо), це відповідало б зміні значення, пов'язаного з вузлом у спрямованому графіку.

Різні вузли, що мають краї цього вузла "значення гучності", автоматично запускаються, і всі необхідні обчислення та оновлення, природно, пульсують через додаток. Додаток "реагує" на стимул користувача. Функціональне реактивне програмування - це просто реалізація цієї ідеї на функціональній мові, або взагалі в рамках парадигми функціонального програмування.

Щоб отримати докладнішу інформацію про "обчислення даних", знайдіть ці два слова у Вікіпедії або скористайтеся улюбленою пошуковою системою. Загальна ідея така: програма - це спрямований графік вузлів, кожен з яких виконує прості обчислення. Ці вузли з'єднані між собою графськими посиланнями, які забезпечують виходи одних вузлів на входи інших.

Коли вузол спрацьовує або виконує його обчислення, вузли, підключені до його виходів, мають відповідні входи "спрацьовані" або "позначені". Будь-який вузол, у якого всі входи спрацьовані / позначені / доступні, автоматично спрацьовує. Графік може бути неявним або явним залежно від того, як саме реалізується реактивне програмування.

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

Реактивне програмування імпортує цю ідею "каскаду виконання" і, здається, мислить програму подібним чином, але за умови, що деякі вузли підключені до "зовнішнього світу" і каскади виконання спрацьовують, коли ці сенсорні -подібні вузли змінюються. Потім виконання програми виглядатиме як щось аналогічне складній рефлекторній дузі. Програма може або не бути в основному силою між подразниками, або може перетворитися в стан, що знаходиться в основному, між подразниками.

"нереактивне" програмування було б програмуванням з дуже різним поглядом на потік виконання та відношення до зовнішніх входів. Це може бути дещо суб'єктивно, оскільки люди, ймовірно, будуть спокушатися сказати все, що реагує на зовнішні входи, "реагує" на них. Але, дивлячись на дух речі, програма, яка опитує чергу подій з фіксованим інтервалом і розсилає будь-які події, виявлені для функцій (або потоків), є менш реактивною (тому що вона відвідує лише введення користувачем через фіксований інтервал). Знову ж таки, тут суть справи: можна уявити, як реалізація опитування з швидким інтервалом опитування в систему на дуже низькому рівні та програмувати реактивним способом поверх неї.


1
Добре, зараз є кілька хороших відповідей. Чи потрібно видалити свою посаду? Якщо я побачу двох або трьох людей, які говорять, що нічого не додає, я видалю його, якщо його корисне число не зросте. Немає сенсу залишати його тут, якщо це не додасть чогось цінного
Томас Каммейер

3
Ви згадали потік даних, так що додає значення IMHO.
Rainer Joswig

Ось що мається на увазі QML, здається;)
mlvljr

3
Для мене ця відповідь була найпростішою для розуміння, тим більше, що використання природних аналогів на кшталт «пульсація через додаток» та «сенсорно-подібні вузли». Чудово!
Акселі Пален

1
на жаль, посилання в Манчестерській системі передачі даних померло.
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зміни, які можна змінити, можна поступово оновлювати. Для прикладу розглянемо операцію по карті 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змінюються.


4
Я був там з вами, поки ви не пішли з дискретними рівняннями. Засновницькою ідеєю FRP був безперервний час , де немає " j+1". Натомість подумайте про функції безперервного часу. Як нам показали Ньютон, Лейбніц та інші, часто глибоко зручно (і "природно" в прямому сенсі) описати ці функції різним, але постійно, використовуючи інтеграли та системи ОДЕ. В іншому випадку ви описуєте алгоритм наближення (і поганий) замість самої речі.
Конал

Мова лайксів шаблонів HTML і обмежень для макета, схоже, виражає елементи FRP.

@Conal це змушує мене замислитись, чим відрізняється FRP від ​​ODE. Чим вони відрізняються?
jhegedus

@jhegedus У цій інтеграції (можливо, рекурсивної, тобто ODE) передбачено один із будівельних блоків FRP, а не цілість. Кожен елемент лексики FRP (включаючи інтеграцію, але не обмежуючись ними) точно пояснюється терміном безперервного часу. Чи допомагає це пояснення?
Конал


29

Відмова: моя відповідь в контексті rx.js - бібліотеки "реактивного програмування" для Javascript.

У функціональному програмуванні замість повторення кожного елемента колекції ви застосовуєте функції вищого порядку (HoFs) до самої колекції. Отже, ідея FRP полягає в тому, щоб замість обробки кожної окремої події створити потік подій (реалізований із спостережуваним *) і застосувати до цього HoF. Таким чином ви зможете візуалізувати систему як трубопроводи даних, що з'єднують видавців з передплатниками.

Основними перевагами використання спостережуваного є:
i) він абстрагує від свого коду стан, наприклад, якщо ви хочете, щоб обробник події був звільнений лише для кожної 'n' події, або припинив стріляти після перших 'n' подій, або почати запускати лише після перших подій "n", ви можете просто використовувати HoFs (фільтр, takeUntil, пропустити відповідно) замість встановлення, оновлення та перевірки лічильників.
ii) це покращує локальність коду - якщо у вас є 5 різних обробників подій, що змінюють стан компонента, ви можете об'єднати їхні спостережувані та визначити один обробник подій на об'єднаному спостерігається замість цього, ефективно поєднуючи 5 обробників подій у 1. Це робить його дуже легко міркувати про те, які події у всій вашій системі можуть впливати на компонент, оскільки всі вони присутні в одному обробнику.

  • Спостережуване - це дуал Ітерабельного.

Ітерабельна - це ліниво споживана послідовність - кожен елемент витягується ітератором всякий раз, коли він хоче його використати, а значить, перерахування визначається споживачем.

Спостережувана - це ліниво створена послідовність - кожен елемент висувається спостерігачеві кожного разу, коли він додається до послідовності, а значить, перерахування визначається виробником.


1
Дякую вам за це прямолінійне визначення спостережуваного та його відмінність від ітерабелів. Я думаю, що часто корисно порівнювати складне поняття з його відомою дуальною концепцією, щоб отримати справжнє розуміння.

2
"Отже, ідея, що стоїть за FRP, полягає в тому, щоб замість обробки кожної окремої події створити потік подій (реалізований із спостережуваним *) і застосувати до цього HoF." Я можу помилитися, але я вважаю, що це насправді не FRP, а скоріше приємна абстракція над дизайнерською схемою Observer, яка дозволяє виконувати функціональні операції через HoF (що чудово!), Хоча все ще призначене для використання з імперативним кодом. Обговорення на тему - lambda-the-ultimate.org/node/4982
nqe

18

Чувак, це чудова геніальна ідея! Чому я не дізнався про це ще в 1998 році? У всякому разі, ось моє тлумачення підручника Фран . Пропозиції найкраще вітаю, я думаю про те, щоб запустити ігровий движок на основі цього.

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 р.), Школа музики Haskell .


10

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


9

Діє як електронна таблиця, як зазначалося. Зазвичай на основі подій, що керуються подіями.

Як і у всіх "парадигмах", новизна є дискусійною.

З мого досвіду розподілених потокових мереж дійових осіб, це може легко стати жертвою загальної проблеми узгодженості стану по всій мережі вузлів, тобто у вас виникає багато коливань і захоплення дивних циклів.

Цього важко уникнути, оскільки деяка семантика передбачає референтні цикли чи мовлення, і може бути досить хаотично, оскільки мережа акторів сходить (чи ні) на якомусь непередбачуваному стані.

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

Все добре весело :).



7

Ця стаття Андре Стальца - найкраще та найяскравіше пояснення, яке я бачив досі.

Деякі цитати зі статті:

Реактивне програмування - це програмування з асинхронними потоками даних.

Крім цього, вам надається дивовижна панель інструментів для комбінування, створення та фільтрації будь-якого з цих потоків.

Ось приклад фантастичних діаграм, які є частиною статті:

Клацніть діаграму потоку подій


5

Йдеться про перетворення математичних даних з часом (або ігнорування часу).

У коді це означає функціональну чистоту та декларативне програмування.

Державні помилки - це величезна проблема в стандартній імперативній парадигмі. Різні біти коду можуть змінювати деякий загальний стан у різні "часи" виконання програм. З цим важко впоратися.

У FRP ви описуєте (як у декларативному програмуванні), як дані перетворюються з одного стану в інший і що це викликає. Це дозволяє ігнорувати час, оскільки ваша функція просто реагує на свої входи та використовує їх поточні значення для створення нового. Це означає, що стан міститься в графіку (або дереві) вузлів перетворення і є функціонально чистим.

Це масово зменшує складність та час налагодження.

Подумайте про різницю між A = B + C у математиці та A = B + C у програмі. У математиці ви описуєте відносини, які ніколи не зміниться. У програмі написано, що "прямо зараз" A є B + C. Але наступною командою може бути B ++, у цьому випадку A не дорівнює B + C. У математичному чи декларативному програмуванні A завжди буде дорівнює B + C, незалежно від того, в який момент часу ви запитуєте.

Отже, усуваючи складності стану, що поділяються, та змінюючи значення з часом. Ви програмуєте набагато простіше міркувати.

EventStream - це EventStream + деяка функція перетворення.

Поведінка - це EventStream + деяке значення в пам'яті.

Коли запускається подія, значення оновлюється за допомогою запуску функції перетворення. Значення, яке це виробляє, зберігається в пам'яті поведінки.

Поведінки можуть бути складені для створення нових форм поведінки, які є перетворенням на N інших поведінок. Це складене значення буде перераховано як вхідні події (поведінки) пожежі.

"Оскільки спостерігачі без громадянства, нам часто потрібні декілька з них, щоб імітувати стан машини, як у прикладі перетягування. Нам потрібно зберегти стан, у якому воно доступне для всіх залучених спостерігачів, наприклад, у шляху змінної вище".

Цитата від - Позбавлення шаблону спостерігача http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf


Саме так я ставлюсь до декларативного програмування, а ви просто описуєте ідею краще, ніж я.
neevek

2

Коротке і чітке пояснення щодо реактивного програмування з'являється на Cyclejs - Реактивне програмування , воно використовує прості та наочні зразки.

[Модуль / Компонент / об’єкт] є реактивним, це означає, що він повністю відповідає за управління власним станом, реагуючи на зовнішні події.

Яка користь від такого підходу? це є інверсія управління , головним чином тому, що [модуль / компонент / об’єкт] відповідає за себе, покращуючи інкапсуляцію, використовуючи приватні методи проти публічних.

Це хороший стартовий момент, а не повне джерело знань. Звідти ви можете перейти до більш складних і глибоких паперів.


0

Ознайомтеся з Rx, Реактивними розширеннями для .NET. Вони зазначають, що з IEnumerable ви в основному 'тягнете' з потоку. Запити Linq над IQueryable / IEnumerable - це задані операції, які 'висмоктують' результати з набору. Але з тими ж операторами через IObservable ви можете писати запити Linq, які "реагують".

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

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


0

FRP - це поєднання функціонального програмування (парадигма програмування, побудована на ідеї всього - це функція), і парадигма реактивного програмування (побудована на ідеї, що все є потоком (філософія спостерігача та спостережуваного)). Це, мабуть, найкраще зі світу.

Перегляньте пост Андре Стальца про реактивне програмування для початку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.