Парадигми програмування |
---|
|
Реактивне програмування — це парадигма програмування, побудована на потоках даних і розповсюдженні змін. Це означає, що у мовах програмування має бути можливість легко виразити статичні чи динамічні потоки даних, а реалізована модель виконання буде автоматично розсилати зміни через потік даних.
Наприклад, в імперативному програмуванні вираз, означає, що отримує результат виконання безпосередньо під час обчислення виразу, і потім, якщо значення і зміняться, це не впливатиме на вже обчислене значення .
Проте, в реактивному програмуванні значення буде автоматично оновлено за новими значеннями, що є протилежністю функційного програмування.
Сучасна програма електронної таблиці є прикладом реактивного програмування. Комірки електронної таблиці можуть мати буквальні значення, або формули типу «=B1+C1», що обчислюються за значеннями інших комірок. Коли б не змінилося значення іншої комірки, значення формули оновлюється автоматично.
Інший приклад — це мова опису апаратури типу Verilog. У цьому випадку, реактивне програмування дозволяє моделювати зміни по ходу їх розповсюдження по електричному ланцюгу.
Реактивне програмування спочатку пропонувалося як засіб простого створення інтерфейсів користувача, анімацій у системах реального часу, але стало загальною парадигмою програмування.
Наприклад, у архітектурі Модель-вид-контролер, реактивне програмування дозволяє змінам у моделі автоматично відображатися у виді, і навпаки.[1]
Реактивні мови програмування можуть різнитись від дуже явних, де потоки даних встановлюються через використання стрілок, до неявних, де потоки даних є похідними від мовних конструкцій, як у імперативному або функційному програмуванні. Наприклад, в неявно викликаному функціонального реактивному програмуванні (FRP) виклик функції може неявно спричинити побудову вузла в графі потоку даних. Реактивні бібліотеки програмування для динамічних мов (такі як «Cells» у Ліспі та «Trellis» для Python'у), можуть будувати граф залежностей через аналіз значень, зчитуваних під час виконання певної функції, дозволяючи специфікації потоку даних бути як неявною, так і динамічною.
Іноді термін «реактивне програмування» стосується архітектурного рівня розробки програм, де окремі вузли в графі потоку даних є звичайними програмами, які взаємодіють одна з одною.
Реактивне програмування може бути чисто статичними, де потоки даних встановлюються статично, або бути динамічним, де потоки даних можуть змінюватися під час виконання програми.
Використання умовних переходів в графі потоку даних може деякою мірою змусити статичний граф потоку даних виглядати як динамічний, і злегка затушувати відмінність. Проте, чисто динамічне реактивне програмування може використовувати імперативне програмування для реконструкції графа потоку даних.
Реактивне програмування можна назвати програмуванням вищого порядку, якщо воно підтримує ідею про те, що потоки даних можуть бути використані для побудови інших потоків даних. Тобто, результуюче значення з потоку даних є інший потік даних, який виконується з використанням тієї ж самої моделі обчислення, що і перший.
В ідеальному випадку всі зміни даних поширюються миттєво, що не може бути забезпечено на практиці. Замість цього може бути необхідно надати різні пріоритети обчислення різним частинам графу потоку даних. Це можна назвати диференційованим реактивним програмуванням.
Наприклад, в текстовому процесорі маркування орфографічних помилок не обов'язково має бути повністю синхронізованим зі вставкою символів. Тут диференційоване реактивне програмування потенційно може бути використане для перевірки орфографії з нижчим пріоритетом, що дозволяє перевірці бути відкладеною, зберігаючи при цьому миттєвість інших потоків даних.
Проте, така диференціація вносить додаткову складність у конструкцію. Наприклад, треба вирішувати, як визначити різні області потоку даних, а також як обробляти події, що проходять між різними областями потоку даних.
Обчислення, чи оцінювання у реактивних програмах не обов'язково засновані на тому ж, що й обчислення у програмах, що будуються на стеках. Замість цього, коли якісь дані змінюються, то зміна поширюється на всі дані, які були залежали від змінених даних. Це поширення зміни може бути досягнуто кількома способами, серед яких, можливо, найприроднішим способом є схема інвалідація/лінива-ревалідація.
Просте наївне поширення змін через стек може викликати проблеми, тому що існує потенційно експоненційна складність оновлення для структур даних певної форми. Одна з таких форм може бути описана як "форма повторюваних діамантів", і має наступну структуру: An → Bn → An + 1, An → Cn → An + 1, де n = 1,2... Ця проблема може бути подолана шляхом поширення інвалідації лише тоді, коли деякі дані ще не інвалідовано, з наступною ре-валідацією даних в разі потреби, використовуючи ледачі обчислення.
Одна проблема, притаманна реактивному програмуванню, це те, що більшість обчислень, які у звичайній мові програмування були б зроблені і забуті, мусять лишатися в пам'яті у вигляді структур даних. Це може зробити реактивне програмування дуже вимогливим до об'єму пам'яті. Проте, дослідження, що називається "зниженням", має потенціал вирішити цю проблему.[2]
З іншого боку, реактивне програмування є формою того, що можна описати як "явний паралелізм", і, таким чином, може бути корисним для використання потужностей паралельного апаратного забезпечення.
Реактивне програмування має принципову подібність до шаблону спостерігач, що зазвичай використовується в ООП. Проте, інтеграція концепцій потоку даних в мову програмування полегшить їх висловлення, і, таким чином, може збільшити ступінь деталізації графа потоку даних. Наприклад, шаблон "Спостерігач" зазвичай описує потоки даних між цілими класами/об'єктами, в той час, як об'єктно-орієнтоване реактивне програмування може націлюватися на членів об'єктів і класів.
Модель обчислення на основі стеку у загальновживаному ООП також не зовсім підходить для поширення потоку даних, тому що випадки "зворотного зв'язку дерева ребер" в структурах даних можуть зробити програму експоненційно складною. Але через його відносно обмежене використання і низьку зернистість, це рідко є проблемою для шаблону Спостерігача на практиці.
let data = { price: 5, quantity: 2 }
// Клас "спостерігач" який реалізовує реактивність
let target = null
class Dep {
constructor () {
this.subscribers = []
}
depend () {
if (target && !this.subscribers.includes(target)){
this.subscribers.push(target)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
Object.keys(data).forEach(key => {
let internalValue = data[key]
// пов'язуємо властивості із спостерігачем
const dep = new Dep()
Object.defineProperty(data, key, {
get() {
// запам'ятовуємо контекст виконання
dep.depend()
return internalValue
},
set(newVal) {
internalValue = newVal
// переобчислюємо всі значення, які залежать на даній властивості
dep.notify()
}
})
})
function watcher(myFunc){
target = myFunc
target()
target = null
}
watcher(() => {
data.total = data.price * data.quantity
})
// приклад виконання
console.log(data.total); // 10
data.price = 20;
console.log(data.total); // 40
Це незавершена стаття про програмування. Ви можете допомогти проєкту, виправивши або дописавши її. |