Céu: Structured Synchronous Reactive Programming (SSRP)

Céu is a Esterel-based synchronous language:

It appeared in LtU in the past in an announcement of the "SPLASH: Future of Programming Workshop" program.

In this new public version, we are trying to surpass the academic fences with a more polished work (docs, build, etc).

In summary:

• Reactive: code executes in reactions to events
• Synchronous: reactions run to completion in discrete logical units of time (there's no implicit preemption nor real parallelism)
• Structured: programs use structured/imperative control mechanisms, such as "await" and "par" (to combine multiple awaiting lines of execution)

Structured programming avoids deep nesting of callbacks letting programmers code in direct/sequential/imperative style. In addition, when a line of execution is aborted, all allocated resources are safely released.

The synchronous model leads to deterministic execution and simpler reasoning, since it does not demand explicit synchronization from the programmer (e.g., locks and queues). It is also lightweight to fit constrained embedded systems.

We promote SSRP as a complement to classical structured/imperative programming like FRP is now to functional programming.

Comment viewing options

interesting abort model

par, par/or, par/and abort model is interesting!

Abortion in synchronous languages

"Zero-delay" abortion is the most distinguishing feature of synchronous languages.

This paper by Gérard Berry gives a nice overview of synchronous abortion.

Synchronous languages

I'm not sure exactly what you mean by "synchronous languages". My system Felix is synchronous in a sense somewhat similar to Ceu, but it has no abortion at all.

In fact, I'm inspired to see if I can implement it. Your system appears to be a sequential version of the Actor model, whilst mine is like a sequential version of Hoare's CSP. Vaguely!

Now, I can easily spawn several coroutines, the control paths are called fibres (lightweight thread analogy). Ceu's control paths are called trails.

A fundamental distinction is that fibres are detached, they never terminate and so the concept of a join is nonsense. However you can certainly *model* joins using channels. For example "at the end" of a coroutine you write a "tick" down a termination channel, and the continuation can count ticks until all the coroutine spawned in a par/and equivalent are read.

Par/or is tricker. It's easy to detect when one coroutine flags an end (just read on tick!). The problem is the other coroutine keep running unless we do something about it. The first thing that comes to mind is a shared flag which starts saying "run" and is changed to "stop" and which is tested after *every* channel I/O operation. (And initially). This is vaguely like Posix thread cancellation.

So now, emit is just multi-write in Felix, and await on an *internal* event is just a read.

The actual order of doing things in Felix may not be quite the same: if several fibres are waiting on an event which is emitted, they will run one at a time but the order is indeterminate.

Synchronous in the sense

Synchronous in the sense that lines of execution share a single/global notion of logical time.
During a time unit, all lines of execution are given the opportunity to react. Time doesn't elapse until all lines of execution yield control.
Orthogonal abortion (i.e., external to a line of execution) is much easier when you can ensure that things are not executing.

synchronous

"Synchronous in the sense that lines of execution share a single/global notion of logical time."

Still not quite sure what that means. With Felix fibres, there is only one thread of control which is exchanged between fibres (by channel I/O usually). So events are totally ordered, even though the precise order is indeterminate. So you can say a "tick" occurs at each event, in particular, channels are synchronous, and I/O operations are synchronisation points.

The synchronous system has the property that there is *always* one fibre executing (otherwise the system terminates).

However Felix also has asynchronous events for timers and sockets. For example you can sleep for a period. In this case the fibre that sleeps yields to some other fibre waiting. It is made active again when the timeout elapses, although it may not begin executing immediately (because some other fibre is running). These events are asynchronous because the reactivation of a sleeping fibre is not under program control, but is controlled externally (by an external timer device in this case, or a network driver in the case of sockets).

With asynchronous events added, the synchronous system can have NO fibres active without terminating.

So I'm not quite sure how this fits into your model. You claim your system is synchronous but it has timer waits. If an event in Ceu is emitted under program control, the system would be synchronous, but with timers added it isn't in my conception. There is still no concurrency, except that you might say a timer timeout "emit" operation is concurrent.

My asynchronous events add a new kind of indeterminacy: synchronous operations are still indeterminate but the "amount" of indeterminacy is highly restricted, for example on a read/write operation it isn't fixed whether the reader or writer goes first, but one of them certainly does.

Anyhow your model is very interesting. I have some issues with global events. Do I understand correctly, events are global? Any trail can await any event?

[BTW: sorry the Berry paper is behind a paywall so I can't get it]

Synchronous terminology

>> Synchronous in the sense that lines of execution share a single/global notion of logical time.

> With Felix fibres, there is only one thread of control which is exchanged between fibres ...
> So events are totally ordered, ...
> So you can say a "tick" occurs at each event ....
> ... although it may not begin executing immediately (because some other fibre is running).

All these properties imply that your system is synchronous following the definition above (which is not mine, btw).

> These events are asynchronous because the reactivation of a sleeping fibre is not under program control, but is controlled externally (by an external timer device in this case, or a network driver in the case of sockets).
> You claim your system is synchronous but it has timer waits ...

Sure, synchronous systems require asynchronous primitives (emitting and awaiting events) and asynchronous systems (threads, actors) require synchronous primitives (locks, queues, etc).
(Again, I'm following the terminology of synchronous languages authors. "Synchronous" is such an overloaded term in CS...)

Synchronous execution is not about the whole system, but about a reaction or logical unit of time: during an unit of time, the execution is synchronous because it is never externally preempted and the program has full control over it.

Global vs Local events

> I have some issues with global events. Do I understand correctly, events are global? Any trail can await any event?

If the event is external, then yes.

But you can declare an internal event with lexical scope:

input void KEY;    // external event (global visibility)
par/or do
event void e;  // internal event (local visibility)
par/or do
await e;   // visible
with
emit e;    // visible
end
with
await e;       // compile error (event "e" is not declared)
end