Skip to content

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

Эта страница — о том, откуда взялась идея, на которой стоит Continuum: какую проблему она решает, кто и когда её формулировал, как она разветвилась на несколько традиций и почему мы выбрали самую старую и самую строгую из них.

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

Обычная программа — это рецепт: шаг за шагом, сверху вниз, от входа к выходу. У неё есть начало и конец, и порядок исполнения совпадает с порядком текста.

Интерактивная система устроена иначе: она не заканчивается. Она сидит и ждёт — клика, клавиши, ответа сети, тика таймера — и должна реагировать на события, приходящие в любом порядке и в любой момент. Дэвид Харел и Амир Пнуэли в 1985 году назвали такие системы реактивными (reactive systems) и противопоставили их трансформационным (вход → вычисление → выход), заметив, что для реактивных систем классические способы описания программ подходят плохо.

Инженерный ответ по умолчанию — колбэки: «когда произойдёт X, выполни Y». Он работает, но на масштабе рождает то, что книга Блэкхита и Джонса о FRP называет шестью бедствиями обработчиков событий:

  1. непредсказуемый порядок — кто из слушателей сработает первым;
  2. пропущенное первое событие — подписались позже, чем оно случилось;
  3. грязное состояние — колбэк застал переменные полуобновлёнными;
  4. нити апдейтов — каскад «обработчик дёргает обработчик», который невозможно проследить;
  5. утечки слушателей — забытые отписки;
  6. случайная рекурсия — обработчик косвенно вызывает сам себя.

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

Предыстория: потоки данных и электронные таблицы

Идея «программа — это граф зависимостей, а не список шагов» старше термина «реактивное программирование» на десятилетия.

  • Dataflow-языки. Lucid (Эшкрофт и Уэйдж, 1976) описывал переменную не как ячейку, а как бесконечную последовательность значений во времени — по сути, прообраз Behavior.
  • Электронные таблицы. VisiCalc (Бриклин и Фрэнкстон, 1979), затем Lotus 1-2-3 и Excel — первое массовое реактивное программирование: формула =A1+B1 — это объявленная зависимость, которую система поддерживает сама. Миллионы людей программировали реактивно, не зная этого слова.
  • Синхронные языки. Французская школа 1980-х — Esterel (Жерар Берри), Lustre (Каспи и Альбвакс, 1987), Signal — создавалась для бортовых систем (авионика Airbus, АЭС). Её ключевая идея — синхронная гипотеза: реакция на событие концептуально мгновенна и атомарна, время дискретно, «одновременно» — строго определённое понятие. Если это звучит как наши транзакции — так и есть: транзакция Continuum — прямой потомок этой идеи.
  • Observer и MVC. В мире ООП та же потребность породила паттерн «наблюдатель» (Smalltalk MVC, конец 1970-х; канонизирован GoF в 1994). Это колбэки, дисциплинированные интерфейсом — но все шесть бедствий при нём остаются: статья «Deprecating the Observer Pattern» (Майер, Ромпф, Одерски, 2010) разбирает это по пунктам.

1997: FRP — Конал Эллиотт и Пол Худак

Functional Reactive Programming родился как статья «Functional Reactive Animation» (Конал Эллиотт, Пол Худак; ICFP 1997) и библиотека Fran на Haskell — для описания анимаций.

Вклад Fran — не «ещё одна библиотека событий», а денотационная семантика: точный математический смысл каждой конструкции.

  • Behavior a — значение в каждый момент времени: функция Time → a. Причём время у Эллиотта непрерывное, как в физике — анимация описывается уравнением, а не циклом кадров.
  • Event a — множество моментов со значениями: [(Time, a)] (имя, которое Continuum позже переименовал в Stream).

Композиция получает смысл алгебры: map, merge, switch, snapshot определены уравнениями, а не описанием реализации. Из этого следуют доказуемые свойства — в частности, отсутствие глитчей: полуобновлённое состояние не существует в модели, а не «редко наблюдается».

Эллиотт до сих пор настаивает, что FRP — это именно два свойства: денотационная семантика + непрерывное время. Всё остальное — «reactive programming», без «functional».

2000-е: развитие в Haskell и первые браузерные опыты

Чистый Fran имел практические проблемы (утечки времени/памяти при наивной реализации), и следующее десятилетие искало рабочие формулировки:

  • Yampa (Нильссон, Кортни, Петерсон, ~2002) — «стрелочный» FRP: вместо сигналов первого класса — функции сигналов (SF a b), что закрыло утечки ценой менее прямого стиля. На Yampa писали роботов и игры.
  • FrTime (Racket, ~2003) и Flapjax (2009, группа Кришнамурти в Брауне) — первые серьёзные попытки принести FRP в браузер; Flapjax был языком-надстройкой над JavaScript.
  • Push-pull FRP (Эллиотт, 2009) — уточнение реализации: события «толкают» (push), поведения «тянут» (pull); ровно эта гибридная схема живёт в Continuum.
  • reactive-banana (Апфельмус, ~2011) и Sodium (Стивен Блэкхит, ~2012) — зрелые библиотеки дискретного («классического») FRP: время не непрерывное, а последовательность транзакций; зато семантика строгая, реализация практичная, а библиотека переносима между языками (Java, C#, TypeScript…). Книга «Functional Reactive Programming» Блэкхита и Джонса (Manning, 2016) — учебник этой ветви и главный источник модели Continuum.

2009–2013: слово «reactive» уходит в народ

Параллельно академической линии термин «реактивное программирование» приобрёл два новых — и разных — значения.

Rx / ReactiveX. Эрик Мейер и команда в Microsoft выпустили Reactive Extensions (Rx.NET, 2009): IObservable<T> как дуал IEnumerable<T>, богатая алгебра операторов над потоками событий. RxJS принёс это в JavaScript, ReactiveX — почти во все языки. Rx гениален как библиотека асинхронных потоков, но это не FRP в смысле Эллиотта: нет Behaviors как отдельного понятия, нет транзакционной одновременности — глитчи в ромбе зависимостей возможны и являются известной ловушкой (combineLatest с общим предком).

Reactive Manifesto (2013, Йонас Бонер и др.) — третье значение слова: архитектура распределённых систем (отзывчивость, устойчивость, эластичность, обмен сообщениями). К программированию интерфейсов отношения почти не имеет; упоминаем, чтобы вы не путались в поиске.

2010-е: реактивность приходит в UI-фреймворки

  • Knockout (2010) — observable и computed с автоотслеживанием зависимостей в браузере; прямой предок сегодняшних сигналов.
  • Elm (Эван Чаплицки, диссертация 2012) — «конкурентный FRP» для GUI; показательно, что в 2016-м («A Farewell to FRP», Elm 0.17) Elm отказался от сигналов в пользу The Elm Architecture — редьюсер-цикла, который позже вдохновил Redux.
  • React (Facebook, 2013) — слово «реактивный» в названии, но модель противоположная FRP: состояние меняется → компонент перезапускается → виртуальный DOM диффится. Реактивность здесь достигается пересчётом всего и сравнением, а не распространением по графу зависимостей.
  • MobX (Вестстрате, 2015), реактивность Vue — «прозрачная» реактивность: чтение значения внутри вычисления автоматически создаёт зависимость (через прокси/геттеры). Удобно; цена — граф зависимостей неявный и динамический, о нём трудно рассуждать.
  • Svelte 3 (2019) — реактивность средствами компилятора.
  • SolidJS (Райан Карниато; 1.0 — 2021) — точечные сигналы (fine-grained) без виртуального DOM: компонент выполняется один раз, обновляются точечные привязки. Solid доказал индустрии жизнеспособность run-once-модели — и запустил «сигнальную волну»: Preact Signals (2022), Angular Signals (2023), предложение TC39 Signals для стандартизации в самом JavaScript (stage 1, 2024).

Две живые традиции — и где стоит Continuum

К 2020-м в UI осталось две основные традиции реактивности:

Сигналы (Solid, Vue, Angular…)Классический FRP (Sodium, Continuum)
Зависимостиавтоотслеживание при чтенииструктура выражения (map/lift)
Графнеявный, динамическийявный, статический по построению
Одновременностьбатчинг как оптимизациятранзакции как семантика
Событиянет отдельного понятия (эффекты)Stream — первоклассное значение
Время«текущее значение»значение во времени; прошлое/настоящее различимы (hold)
РодословнаяKnockout → MobX → SolidFran → Sodium → сюда

Continuum сознательно выбирает вторую: Behaviors + Streams + транзакции из дискретного FRP (Sodium), run-once компоненты и точечные обновления DOM, чью практичность доказал Solid, и дерево владения для жизненного цикла. Непрерывное время Эллиотта тоже здесь — integral/warp для анимаций, с animationFrames() как дискретизацией.

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

2026: State и скоупы

В июле 2026 Continuum переименовал Behavior в Wire («провод»): имя из FRP-литературы честно указывало на родословную, но объясняло меньше, чем метафора провода, по которому всегда течёт текущее значение.

Метафора не пережила и месяца: вне проекта «провод» никто не узнавал, и в том же июле Wire стал State — словом, которым уже думают разработчики, приходящие из React, Vue и Svelte. Рассматривался и Signal, но был отклонён: у Continuum нет автоотслеживания зависимостей, и это имя обещало бы семантику Solid. Старые имена — Event/newEvent, Behavior, Wire/wire/WireSource — остаются депрекейтнутыми алиасами до 1.0.

Одновременно модель владения получила явную формулировку — «значения — формулы; состояние и эффекты принадлежат скоупу»: чистые производные вычисляются по спросу и спят без слушателей, а hold/accum/perform регистрируются у владельца — скоупа компонента или явного root() на уровне модуля.

Что читать

  • Conal Elliott, Paul Hudak — Functional Reactive Animation (ICFP 1997) — первоисточник.
  • Conal Elliott — Push-Pull Functional Reactive Programming (2009).
  • Stephen Blackheath, Anthony Jones — Functional Reactive Programming (Manning, 2016) — модель, на которой стоит Continuum; наш конспект с отсылками к главам.
  • David Harel, Amir Pnueli — On the Development of Reactive Systems (1985) — откуда слово «реактивный».
  • Ingo Maier, Tiark Rompf, Martin Odersky — Deprecating the Observer Pattern (2010) — почему колбэки не чинятся дисциплиной.
  • Evan Czaplicki — A Farewell to FRP (2016) — честный рассказ о том, почему Elm ушёл от сигналов.
  • Ryan Carniato — статьи о точечной (fine-grained) реактивности (solidjs.com/blog) — сигнальная традиция из первых рук.

Незнакомый термин? Вся терминология этой документации объяснена в глоссарии.