Skip to content

Глоссарий

Вся терминология документации в одном месте, простыми словами. Термины сгруппированы по темам; внутри страниц на них можно ссылаться якорями.

Модель времени

State

Значение во времени, состояние. У State в любой момент можно спросить «какое ты сейчас?» — и ответ будет всегда: sample() отвечает даже без единого слушателя. Текст поля ввода, текущий пользователь, позиция мыши: они есть в каждый момент, даже когда никто не смотрит. Формально — функция из времени в значение (Time → A). Создаётся через state(init) (ячейка с .set и .update((state) => next) — апдейтер фолдится над значением, застейдженным текущим моментом, так что несколько update в одном batch компонуются) или выводится из других значений (map, combine). В FRP-литературе этот тип называется Behavior; раньше в Continuum он назывался Wire. Подробнее: States.

Behavior

Депрекейтнутый алиас State. Имя из FRP-литературы (Эллиотт и Худак, 1997): в статьях и книгах «значение во времени» называется Behavior. В коде Continuum алиасы Behavior и Wire (промежуточное имя этого типа) ещё работают, но удаляются в 1.0 — пишите State.

Stream

Поток происшествий. В отличие от State, у события нельзя спросить «какое ты сейчас» — между срабатываниями его просто нет. Клик, нажатие клавиши, ответ сервера: это не значения, которые существуют, а вещи, которые случаются. Подробнее: Streams.

Вхождение (occurrence)

Одно срабатывание события. Пара «момент времени + значение»: третий клик, конкретный ответ сервера. Когда мы говорим «Stream — это дискретные вхождения», это значит: событие — набор отдельных случаев срабатывания, а не непрерывная величина.

Дискретный

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

Момент / транзакция

Один атомарный «тик» логического времени. Всё, что следует из одного события — все производные значения, — пересчитывается внутри одного момента, как единое целое: снаружи нельзя увидеть «половину» обновления. «Логическое» время — потому что моменты считаются не миллисекундами, а порядком: важен только до/после/одновременно. Подробнее: Транзакции и время.

Одновременность

Два вхождения в одном моменте. В колбэчном мире «одновременные» события на самом деле выполняются в каком-то случайном порядке. В Continuum одновременность настоящая: два события в одном моменте видят один и тот же мир, а их слияние (merge) требует явно сказать, что делать с парой.

Коалесинг (coalescing)

Слияние одновременных вхождений в одно. Когда два события случились в один момент, а на выходе нужно одно, — функция коалесинга ((a, b) => … в Stream.merge) сворачивает их. Без неё пришлось бы выбирать «кто первее» — а это уже гонка.

Глитч

Наблюдаемое полуобновлённое состояние. Классический баг реактивных систем: sum = a + b, где a уже обновилось, а b ещё нет, и на миг sum показывает бессмыслицу. В Continuum глитчи невозможны по построению — момент атомарен. Пример с «ромбом»: Транзакции и время.

Ромб (diamond)

Схема зависимостей, в которой глитчи проявляются. Одно значение питает две ветки, ветки сходятся в общем потомке: count → doubled, count → squared, doubled + squared → sum. Наивная реактивность пересчитает sum дважды (со вторым разом — исправление); транзакция — один раз, сразу правильно.

Задержка hold

Правило: State обновляется на границе момента. Внутри того момента, в котором пришло событие, hold/accum ещё показывают старое значение. Звучит странно, но именно это делает «текущее значение» определённым, когда на него смотрят из события, которое его же меняет: прошлое стабильно, настоящее ещё формируется. Следствие: ячейка доставляет одно коалесцированное вхождение updates на момент. Подробнее: Транзакции и время.

Свёртка (fold)

Накопление истории события в одно значение. clicks.accum(0, (_e, n) => n + 1) — «начни с 0 и на каждый клик прибавляй 1»: счётчик — это свёртка кликов. Тот же приём, что Array.reduce, только по времени, а не по массиву. Идиоматичная форма переходов — state(0).on(clicks, (n) => n + 1): редьюсер (состояние, событие), процесс регистрируется в текущем скоупе.

push / pull

Два способа доставки значений. События толкают (push): случилось — поехало по графу. States тянут (pull): значение вычисляется, когда его спрашивают — чистые производные это рецепты, которые спят без слушателей и считаются по спросу. Continuum — гибрид: изменения распространяются толчками, чтение sample() — вытягиванием.

Сеть

Сеть / граф зависимостей

Всё, что вы построили из States и Streams. map, combine, merge, at соединяют величины в направленный граф: узлы — значения и события, рёбра — «вычисляется из». Компонент строит свой кусок графа один раз; дальше по нему текут обновления.

Ранг (rank)

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

at

Событие фотографирует значение. draft.at(clicks) — «в момент клика возьми текст поля»; с преобразованием — draft.at(clicks, (val, ev) => …), колбэк получает (значение, событие). FRP-замена привычке «прочитаю из переменной внутри обработчика», но с точной семантикой: at видит значение до текущего момента (см. задержка hold). В FRP-литературе эта операция называется snapshot.

sample

Прочитать текущее значение State напрямую. s.sample() — для кода вне сети: инициализация, тесты, интеграция с чужим кодом; отвечает всегда, даже когда у значения нет ни одного слушателя. Внутри сети предпочитайте at — у него определена одновременность.

Люк (hatch)

Явная дверь между чистой сетью и внешним миром. Внутрь: stream / state (впрыснуть значение через .fire / .set). Наружу: listen (выполнить побочный эффект), perform (сходить в IO и вернуть результат событием). Слово подчёркивает: границы сети видны в коде, а не размазаны.

Result

Ошибка как данные. { ok: true, value } | { ok: false, error } — результат IO, где неудача — обычная ветка значения, а не выброшенное исключение. Ошибку можно фильтровать, копить, показывать — как любые данные.

Рендеринг

Точечные обновления DOM (fine-grained rendering)

Обновляется ровно то, что зависит от значения. Не «перерисуем компонент и сравним», а «этот текстовый узел привязан к этому State — его и патчим». Отсюда run-once компоненты и отсутствие виртуального DOM.

Привязка (binding)

Живая связь State → кусочек DOM. {count} в JSX — не «вставить текущее значение», а «этот текстовый узел теперь навсегда показывает count». То же с атрибутами: class={cls}.

Ре-рендер

Чужое понятие (React), от которого мы отталкиваемся. Повторный запуск функции компонента ради вычисления нового дерева. В Continuum его нет: компонент выполняется один раз, обновления идут по привязкам.

Динамический регион

Участок DOM, который перестраивается по значению. Примитив dyn (и сахар над ним — Show, Dynamic): между маркерами-комментариями живёт поддерево; изменилось управляющее значение — старое поддерево уничтожается, строится новое. Значения меняют привязки, структуру меняют регионы. Подробнее: Условный рендеринг.

Keyed reconciliation / LIS

Сопоставление строк списка по ключу и минимальные перестановки. <Each> держит по живому поддереву на ключ; при изменении порядка вычисляет самую длинную возрастающую подпоследовательность (LIS) и двигает только то, что действительно переехало. Подробнее: Списки.

last-request-wins

Правило гонки ответов: побеждает последний запрос. Если пользователь запросил страницу 2, а потом страницу 3, поздно пришедший ответ на «2» игнорируется. В resource это решено внутри — нумерацией запросов.

Чанк / code splitting

Разрезание бандла на куски, загружаемые по требованию. Литеральный import("./Page") в коде — сигнал сборщику выделить отдельный файл (чанк), который скачается при первом визите. У нас — через lazy в роутере.

Жизненный цикл

Дерево владения (ownership tree)

Кто создал — тот и уберёт. Всё, что компонент заводит (подписки, таймеры, вложенные регионы), регистрируется у его «владельца»; уничтожение поддерева каскадно освобождает ресурсы — сначала детей, потом родителя. Подробнее: Владение и жизненный цикл.

Скоуп (scope)

Один узел дерева владения; владелец состояния и эффектов. Каждый компонент, каждый динамический регион, каждая строка <Each> создаёт свой скоуп: у него свои onCleanup/onMount, и умирает он целиком. Всё, что хранит состояние или производит эффекты — hold, accum, .on, perform, — регистрируется в текущем скоупе и живёт ровно столько же.

root()

Явное время жизни модульного состояния. Внутри компонента владелец есть автоматически; на уровне модуля его нет — поэтому hold/accum/ perform вне компонента оборачивают в root(() => …): он создаёт скоуп верхнего уровня (и отдаёт dispose для явной уборки). Вызов без владельца — обучающая ошибка, а не тихая утечка.

«Значения — формулы; состояние и эффекты — скоупу»

Два закона модели владения. Чистые производные (map, combine, filter, at) — формулы-рецепты: вычисляются по спросу, спят без слушателей, sample() всегда отвечает — им владелец не нужен. Всё, что хранит историю или трогает внешний мир (hold, accum, .on, perform), требует владельца: в компоненте — автоматически, на уровне модуля — явный root(). Подсказка retain() при этом — чисто перф-оптимизация (держать цепочку тёплой при мигающих слушателях), на семантику она не влияет.

Граница ошибок (error boundary)

Страховка для куска страницы. Если построение или пересборка поддерева бросили исключение, ближайший <Catch> над ним уничтожает сломанное поддерево (со всеми подписками) и показывает fallback вместо падения всего приложения. Подробнее: Условный рендеринг.

Каскадная уборка

Уничтожение поддерева освобождает всё внутри автоматически. Ушла страница — отписались её подписки, остановились таймеры, исчезли модалки в порталах. Не нужно помнить, что «при размонтировании надо…» — дерево владения помнит за вас.

Контекст

Значения, доступные потомкам без протаскивания через пропсы. provide кладёт значение в текущего владельца, use ищет вверх по дереву. Подробнее: Контекст.

Внешний контекст

Классический (дискретный) FRP

Ветвь FRP, к которой относится Continuum. Время — последовательность транзакций, у комбинаторов точная семантика (Sodium, книга Блэкхита и Джонса). Противопоставляется и непрерывному FRP Эллиотта (время как в физике), и «реактивности» сигналов/Rx. См. Историю и контекст.

Денотационная семантика

Определение конструкций через математический смысл, а не через реализацию. «merge — это объединение множеств вхождений с коалесингом одновременных» — уравнение, из которого можно доказывать свойства (например, отсутствие глитчей), а не описание цикла в коде.

Сигналы

Соседняя традиция реактивности (Solid, Vue, Angular, TC39). Зависимости отслеживаются автоматически при чтении значения; граф неявный. Удобно писать; сложнее рассуждать. Сравнение традиций — в Истории и контексте.

Синхронная гипотеза

Идея синхронных языков 1980-х: реакция концептуально мгновенна. Пока система реагирует на событие, новые события «ждут снаружи» — поэтому реакция атомарна. Транзакции Continuum — прямое наследие этой идеи.