On this page:
2.1 A First Esterel in Racket Program
2.2 Using Racket Constructs When Possible
2.3 Runtime Errors
2.4 Signals as Values
2.5 Onwards from Here

2 Esterel in Racket, for Esterel Programmers🔗ℹ

The primary design goal for Esterel in Racket is to faithfully implement the semantics of Kernel Esterel (including instantaneous reaction to absence) while providing as seamless an integration with Racket under that constraint as possible. As such, Esterel in Racket takes on many of the large scale characteristics of Racket. This section details those design decisions and how they play out when using Esterel in Racket.

Each section contains running examples that are meant to be played around with. The simplest way to do so is to use DrRacket, which comes with the default Racket download.

2.1 A First Esterel in Racket Program🔗ℹ

As a first example, here’s ABRO: simply paste the following code into DrRacket’s definitions window and hit the Run button to see a few instants run.

#lang racket
(require esterel/full)
 
(define-signal A B R O)
 
(define abro
  (esterel
   (loop (par (await (present? A))
              (await (present? B)))
         (emit O)
         #:each (present? R))))
 
(react! abro)
(react! abro #:emit (list A B))
(react! abro #:emit (list A))
(react! abro #:emit (list R))

The first line declares the language and the second line loads the Esterel in Racket implementation. Each Esterel program has to be encapsulated inside esterel; this is a dynamic restriction, not a static one. Code inside esterel can call arbitrary helper functions and, indeed, things like loop and emit are defined as Racket macros and functions inside the Racket library esterel/full.

Code inside esterel doesn’t run when the esterel form is evaluated, however. Instead, it returns a value that encapsulates the Esterel computation. When that value is passed to the function react!, a single instant is run and the values of signals are returned via a table that maps signals to booleans (if they are not valued signals) or to their values (if they are).

2.2 Using Racket Constructs When Possible🔗ℹ

In general, Esterel has been pared down, preferring to use a Racket construct when it already exists instead of building up a parallel set of constructs for Esterel programs. For example, we simply use begin (or the implicit begin found in many constructs) instead of having an explicit seq construct. And, while there is a loop construct provided by Esterel in Racket, it is a simple layer over the more conventional Racket named let that defines a recursive function.

So, to abstract over repeated patterns in Esterel code, we simply define Racket functions. As an example, consider the function Aux, that demonstrates a few more features of Esterel in Racket, using every with with the keywords #:immediate and #:do.

(define (Aux I O)
  (every #:immediate (present? I)
         #:do
         (pause)
         (emit O)))

The function Aux is an ordinary Racket function and it consumes two parameters, named I and O. The parameters are meant to be signals and thus it must be called from within the dynamic extent of esterel.

Here is some code that defines two signals and then, in parallel, invokes Aux twice; once with I as S1 and O as S2 and once the other way around.

(define-signal S1 S2)

 

(define r
  (esterel
   (emit S1)
   (par (Aux S1 S2)
        (Aux S2 S1))))

 

> (react! r)

'#hash((#<signal: S2> . #f) (#<signal: S1> . #t))

> (react! r)

'#hash((#<signal: S2> . #t) (#<signal: S1> . #f))

> (react! r)

'#hash((#<signal: S2> . #f) (#<signal: S1> . #t))

As you can see, this Esterel program alternates between emitting S1 and S2, instant by instant.

2.3 Runtime Errors🔗ℹ

ThereRacket does have a sister language Typed Racket (see the Typed Racket documentation) that could be used to syntactically rule out certain misuses of Esterel in Racket operations; so far, we have not investigated this but hope to eventually. are very few syntactic errors in Esterel in Racket programs; instead, programs that are erroneous will raise errors as they execute instead of being ruled out by a compiler statically.

As one example of a runtime error, here is what happens when Aux is called from outside of esterel:

> (Aux S1 S2)

present?: contract violation;

 must be run from within the dynamic extent of `esterel`

  in: (->*

       (signal?)

       (#:pre natural?)

       #:pre/desc

       ...

       boolean?)

  contract from:

      <pkgs>/esterel-lib/esterel/kernel.rkt

  blaming: top-level

   (assuming the contract is correct)

  at: <pkgs>/esterel-lib/esterel/kernel.rkt:50:3

This is a dynamic check that happens inside present?, and several other Esterel functions. They check if they are being used in the dynamic extent of esterel and, if not, raise an error. The function in-esterel? returns #t when code is currently executing during an instant and #f otherwise.

2.4 Signals as Values🔗ℹ

Signals are simply values at the Racket level and can be passed to and returned from Racket functions, as other Racket values can. This means that a signal is not equated with a boolean indicating that it is present or absent, as it is in normal Esterel code. Instead, the signal has to be passed to present?, which returns a Racket boolean that indicates its presence of absence; thus we have the opportunity to use boolean-accepting Racket combination operations (like and and or, etc) before passing the boolean on to operations like suspend.

Still, in order to correctly identify nonconstructive programs, signals must be associated with a specific scope, disallowing emits after the lexical environment in which a signal is declared has been exited.

Because we’re in the larger context of Racket, we do not rule out this kind of program statically. Indeed, it is fine for a signal to leave the lexical environment where it is created, as long as it does not leave the dynamic extent of the with-signal. For example, we may wish to pass it to a helper function, as in the example above when we used Aux.

More generally, a program where signals flow around the program entering and leaving other functions and being stored in data structures is all be fine, as long as the with-signal form does not terminate before the signal is emitted.

Thus, we have a dynamic check associated with emit that signals an error when a signal has outlived the dynamic extent of its creation. Here’s an example. The signal s1 is created by the with-signal form and then is returned from it and ends up being bound to the identifier a-signal-outside-its-extent.

> (react!
   (esterel
    (define a-signal-outside-its-extent
      (with-signal (s1)
        s1))
    (emit a-signal-outside-its-extent)))

emit: signal dead;

 the dynamic extent of the `with-signal` has ended

  signal: #<signal: s1 (0)>

At this point, since the with-signal form has exited, Can should be able to know that the signal is not going to be emitted. To ensure that that deduction that Can makes is legal, emit raises an error instead of emitting the signal.

2.5 Onwards from Here🔗ℹ

The documentation for the esterel/kernel library contains a reference-style manual description for the primitives from Kernel Esterel. Each blue box gives an overview of a construct and is followed by a technical description of how it behaves. In each case, the examples will run in a file that starts with
 #lang racket
(require esterel/kernel) 

The documentation for the esterel/full is similar, but it reexports everything from esterel/kernel as well as adding in some of the constructs from full Esterel.

See also The Racket Guide for an overview of Racket.