Тем не менее, хоть управлять "хаосом" неизбежно нужно, но и в стиле примитивов языка Esterel это тяжело, и, прежде всего, для задач именно dataflow. Достаточно взглянуть на демки того же Ceu (к примеру фиг.9):
http://www.ceu-lang.org/chico/ceu_rem13_pre.pdfДля случаев условно "не анонимности" появляется возможность автомат "локализовать" в виде функции, явно выделив его входы/выходы в виде аргументов/результата функции. Автоматы-функции ("акторы") организуются как в обычном коде, формируя иерархические КА. Появляется возможность явно выделить понятие "такта" (у них там это clock). Изначально такая явная "dataflow"-модель в виде языков Lustre и Signal использовалась для типовых расчётных задач, числомолотилок, или аля аудио/видео декодинг и т.п. Но, в целом, ранее был представлен Prelude с целым "шедулингом". Или вот для управления "хаосом":
https://hal.inria.fr/hal-01509314/documentОпять же, я не трогаю embedded специфику. Уделю внимание универсальным концепциям, подходящим для всех "смертных".
Как пример впихивания Lustre-семантики в обычный императивный (ну и функциональный) ML -- язык Lucid Synchrone
https://www.di.ens.fr/~pouzet/lucid-syn ... manual.pdfИзначально языки Lustre и Signal имели чуть разную семантику (ну, отличается и синтаксис, и чуть разный подход в организации базовых конструкций). Сейчас же с учётом всяких расширений можно сказать, что принципиальная разница стёрта. Lustre изначально позиционировался для числомолотилок, отсюда для упрощения требовалось, чтобы все входные аргументы всегда как бы были указаны (как при вызове обычных С-функций, т.е. не было опциональных данных, хотя косвенно таковы подразумевались. За подробностями -- лучше в ориг. доки). А Signal проектировался для выражения "железных" автоматов -- функция должна "реагировать" как только на входе покажутся необходимые данные (необязательно все, название "signal" -- как выражение отношений между сигналами). И сейчас весь код в Lustre-like строится так, чтобы не было строгой необходимости явно указывать отсутствие данных (и, в целом, тамошние языки проектируются выдавать на выхлопе не только C/C++/Java/байт-код и т.п., но и HDL-спецификации железа или какой там нужен формализм для платформ). С учётом поддержки гибридных автоматов, фактически, полностью покрывается семантика агрегатов Бусленко.
В основе для critical hard realtime жёсткие ограничения для базовых возможностей. Это заметно, например, по таким вещам, как работа с массивами (статические, и других контейнеров нет. Кстати, тут есть и удобная "функциональщина"):
http://laure.gonnord.org/pro/research/E ... arrays.pdfhttps://link.springer.com/content/pdf/1 ... F59130.pdfТамошние "node" в Lustre-языках выглядят как:
Код:
node sum(x: int) = (y: int)
let
y = x + (0 fby y);
tel
что, по сути, можно трансформировать в такой класс (аля Java):
Код:
class Sum {
int m_y;
void reset() {m_y = 0;}
int step(int x) {
int y = x + m_y;
m_y = y;
return y;
}
}
Т.е. в языке обычные функции -- комбинаторные автоматы (без состояний), а "node" -- те же функции, но с состоянием ("побочными эффектами"). Исходный рабочий код потом перестраивается под платформу. Т.е. там, где, к примеру, есть вызов ф-ции аля "a = sum(x, y)" на самом деле будет построен "функтор" (в терминах С++). Однако, в отличие от библиотечных фреймворков в мейнстриме для реализации всяких автоматов/акторов и прочей "реактивности" здесь не используются какие-то явные списки/графы акторов или зависимостей по сигналам и во время runtime по ним гоняются данные. Во время compile time выявляются все зависимости, всё "инлайнится", оптимизируется и т.п.
Что не мешает и реальному "ООП":
https://pdfs.semanticscholar.org/f570/9 ... 83acca.pdfВ статье выше есть примечательный момент -- использование оператора "last". В Lustre подразумевается, что при каждом вызове (такте) всегда данные должны быть актуальными. Т.е., как в обычном императивном языке компилятором отслеживается использование неинициализированной переменной, здесь этот подход расширяется на каждый такт автомата. Опциональные данные могут быть оформлены в виде спец-типа вида "signal[t]" или "sig[t]", что соответствует типовому ATD вида "Option[t] = None | Some[t]". Чтобы не заниматься по требованию компилятора каждый раз проверкой, используют оператор last -- последнее значение, полученное неважно когда. К примеру:
Код:
let some_proc(...) = res where
{
init a = 0;
...
res = any_func(last a);
}
(кстати, в ML-like "уравнения" вида "where ... and..." лишь для удобства когда нужно, чтобы не лепить явно последовательность через ";"). Или для входного аргумента:
Код:
let some_proc(a: sig[int] ...) = res where
{
last a = 0;
...
res = any_func(last a);
}
что означает "установить "а" в ноль, если в первом такте не было данных". Для явной проверки кроме типового "match ... with ..." есть конструкция "present" (чтоб не возиться с явными Some|None "классическим" способом):
Код:
present
| A(x) && x > 0 => emit Q = some_proc(x)
| A(x) && B(y) && x <> y =>
do
...
until C(v) && v > 0 // "интервальная логика"
end
Семантика конструкции "явных" автоматов ("automaton" или "mode" в док-ах) подтверждает "Esterel-процессный" подход:
Код:
let switch(low, high) = o where
mode
|Init -> do o = 0 until low(val u) then Up(u)
| Up(u) ->
do o = last o + u
until high(val v) then Down(v)
| Down(v) ->
do o = last o - v
until low(val w) then Up(w)
end
где процесс имеет одно поведение, при наступлении какого-то условия процесс меняет поведение, т.е. на тот же вход реагирует по-другому. Есть слабое "вытеснение" -- оператор until -- автомат должен выдать результат и затем при потребности перейти в новое состояние, и сильное -- unless -- переход в другое поведение перед началом такта. Для перехода есть и оператор continue (продолжить или возобновить состояние), then означает "сброс" в начале (т.е. работать каждый раз сначала).
Для "сброса" также есть оператор вида "reset ... every ...", что означает "сброс" по всей иерархии "нод"-ов.
Фактически, "потоковая" Esterel-модель является альтернативой таким как Sigma-C и StreamIt:
http://www.cs.ucy.ac.cy/dfmworkshop/wp- ... essors.pdfно семантика и возможности значительно шире. К тому же в формализмах выше "ключевые слова" и прочие спец-конструкции лишь "протокол о намерениях" -- нет возможности полного контроля сопровождаемого С-кода.