On this page:
3.1 Running Esterel Code
esterel
react!
esterel?
in-esterel?
exn:  fail:  not-constructive?
debug-when-must
3.2 Signals
with-signal
define-signal
make-global-signal
signal?
signal-name
signal-index
signal-combine
present?
signal-value
emit
3.3 Control Operations
par
pause
suspend
with-trap
exit-trap
trap?
exec

3 Kernel Esterel Reference🔗ℹ

 (require esterel/kernel) package: esterel-lib

The esterel/kernel and esterel/full libraries provide all of the names documented here; the esterel/full library provides additional functionality.

3.1 Running Esterel Code🔗ℹ

syntax

(esterel maybe-pre expr ...)

 
maybe-pre = 
  | #:pre pre-count-expr
Returns a value that, when passed to react!, will evaluate the exprs in a context where in-esterel? returns #t.

If present, the value of pre-count-expr is expected to be a natural number. It is a limit on the history that’s saved for signals in previous instants. It defaults to 0.

procedure

(react! r [#:emit signals])

  
(hash/dc [s signal?]
         [v (s) (if (signal-combine s)
                    any/c
                    boolean?)]
         #:immutable #t #:kind 'flat)
  r : esterel?
  signals : 
(listof
 (or/c (and/c signal? (not/c signal-combine))
       (cons/c (and/c signal? signal-combine)
               any/c)))
 = '()
Runs one instant of r.

If signals are supplied, they are emitted at the start of the instant; valued signals must be paired with values.

The result has the values of signals that were emitted. Additionally if a signal is not a valued signal and the computation depends on it not being present (e.g., if it is passed to present?), it is included in resulting hash, mapped to #f.

If the code is not constructive, an exception (that is recognized by exn:fail:not-constructive?) is raised.

Examples:
> (define-signal S1 S2)
> (react! (esterel (emit S1)))

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

> (react! (esterel (if (present? S1) (void) (emit S2))))

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

> (react! (esterel (if (present? S1) (void) (emit S2)))
          #:emit (list S1))

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

> (react! (esterel (if (present? S1) (emit S1) (void))))

react!: the program is not constructive

 the signal blocking progress can be emitted

  signal: #<signal: S1>

procedure

(esterel? v)  boolean?

  v : any/c
Recognizes the result of esterel.

procedure

(in-esterel?)  boolean?

Returns #t if it is called in the dynamic extent of one of the expressions in a esterel.

procedure

(exn:fail:not-constructive? v)  boolean?

  v : any/c
Recognizes the exception that react! raises when an instant is not constructive.

syntax

(debug-when-must e1 e2 ...)

Evaluates e1 and e2s when the entire debug-when-must form must be executed. Raises an error if evaluated outside of an esterel form.

This can be used to debug Esterel in Racket programs. Sometimes, code inside esterel is run as part of an exploration to determine if signal might be emitted and, in that case, we do not know that that code must run. In such situations, effectful code (such as printf) can run multiple times, leading to confusing behavior. Wrapping such debugging IO operations in a debug-when-must form can help to understand an Esterel in Racket program.

In this example, the use of debug-when-must prevents "hi!" from being printed out, as that code runs only in can exploration. In contrast "bye!" is printed out because that code must run, as S1 is not present. Note that without the debug-when-must forms wrapped around the calls to printf, more printouts happen.

Examples:
> (define-signal S1)
> (react! (esterel (if (present? S1)
                       (debug-when-must (printf "hi!\n"))
                       (debug-when-must (printf"bye!\n")))))

bye!

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

3.2 Signals🔗ℹ

syntax

(with-signal (signal ...)
  body-expr ...+)
 
signal = signal-id maybe-combine
     
maybe-combine = 
  | #:combine combine-expr
  | #:init init-expr #:combine combine-expr
  | #:memoryless #:init init-expr #:combine combine-expr
  | #:single
Creates new signals and binds them to the the signal-ids.

Each signal suffixed with #:combine is a value-carrying signal, and those without are not. Multiple emissions of the signal are combined using the result of combine-expr, a binary function that is assumed to be associative and commutative. If init-expr is provided, then the value of the signal if it is never emitted is the value of init-expr. Once the signal is emitted, however, the value of init-expr is discarded.

If #:memoryless is supplied, the signal’s value is not carried forward from previous instants, but instead restarts with the value of init-expr. If #:memoryless is not supplied, and the signal is not emitted in the current instant, then its value is the value it had in the previous instant (if it had one).

If the signal is followed by #:single, it is also a valued signal, but it may be emitted at most once in each instant and it takes that value.

If with-signal is invoked from within esterel, then the signals may not be emitted once the last body-expr is evaluated (it will result in an error from emit if they are).

The result of the with-signal expression is the result of the last expression. If with-signal is used in the dynamic extent of esterel, the last body-expr is not in tail position with respect to the with-signal, but otherwise it is.

Examples:
> (react!
   (esterel
    (with-signal (s1 s2)
      (unless (present? s2)
        (emit s1)))))

'#hash((#<signal: s2 (1)> . #f) (#<signal: s1 (0)> . #t))

> (react!
   (esterel
    (with-signal (s1 s2 #:combine + s3 s4 #:combine *)
      (emit s1)
      (emit s2 22)
      (emit s2 33))))

'#hash((#<signal: s1 (0)> . #t) (#<signal: s2 (1)> . 55))

> (react!
   (esterel
    (with-signal (s1 #:combine +
                  s2 #:init 11 #:combine +
                  s3 #:combine +
                  s4 #:init 22 #:combine *)
      (emit s1 (signal-value s2 #:can (set s3 s4)))
      (emit s4 33)
      (emit s3 (signal-value s4 #:can (set))))))

'#hash((#<signal: s4 (3)> . 33)

       (#<signal: s3 (2)> . 33)

       (#<signal: s1 (0)> . 11))

syntax

(define-signal signal ...)

Creates signals and binds them to the signal-ids in each signal.

The signals that define-signal creates have indefinite extent (i.e., the signal will not become dead unlike the signals created by with-signal), but define-signal can be used only at the module top-level or at the interactive top-level.

procedure

(make-global-signal name    
  [#:combine combine]    
  #:init init    
  [#:memoryless memoryless])  signal?
  name : string?
  combine : #f = (or/c #f (procedure-arity-includes/c 2))
  init : any/c
  memoryless : #f = boolean?
Creates a global signal named name. If combine is not #f, creates a valued signal. The init argument is not required. If it is not supplied, then the signal has no initial value, otherwise the initial value is init. If memoryless is #t, the signal is memoryless; see with-signal for more information about memoryless signals.

Use make-global-signal when the number of signals is not known ahead of time or it is not convenient to write a sequence of define-signal definitions.

Examples:
> (define sigs
    (for/hash ([i (in-range 10)])
      (values i (make-global-signal (~a i)))))
> sigs

'#hash((0 . #<signal: 0>)

       (1 . #<signal: 1>)

       (2 . #<signal: 2>)

       (3 . #<signal: 3>)

       (4 . #<signal: 4>)

       (5 . #<signal: 5>)

       (6 . #<signal: 6>)

       (7 . #<signal: 7>)

       (8 . #<signal: 8>)

       (9 . #<signal: 9>))

procedure

(signal? v)  boolean?

  v : any/c
Determines if v is a signal, i.e. returned from signal.

Examples:
> (with-signal (s1)
    (signal? s1))

#t

> (signal? "not a signal, but a string")

#f

procedure

(signal-name s)  (and/c string? immutable?)

  s : signal?
Returns the name of a signal.

Examples:
> (define-signal S)
> (signal-name S)

"S"

procedure

(signal-index s)  (or/c #f natural?)

  s : signal?
Returns the index of a signal. This index counts the number of times the with-signal that introduced s has been executed to produce this particular signal. If the signal was created outside the dynamic extent of esterel, signal-index returns #f.

Examples:
> (define-signal O)
> (signal-index O)

#f

> (define r
    (esterel
     (loop
      (with-signal (S)
        (if (present? S)
            (emit O)
            (void))
        (pause)
        (emit S)))))
> (for/set ([(k v) (in-hash (react! r))])
    (list (signal-name k) (signal-index k) v))

(set '("S" 0 #f))

> (for/set ([(k v) (in-hash (react! r))])
    (list (signal-name k) (signal-index k) v))

(set '("S" 1 #f) '("S" 0 #t))

> (for/set ([(k v) (in-hash (react! r))])
    (list (signal-name k) (signal-index k) v))

(set '("S" 1 #t) '("S" 2 #f))

procedure

(signal-combine s)  (or/c #f (-> any/c any/c any/c))

  s : signal?
Returns the combining operation for s, or #f if s is not a value-carrying signal.

Example:
> (with-signal (s1 s2 #:combine +)
    (values (signal-combine s1)
            (signal-combine s2)))

#f

#<procedure:+>

procedure

(present? s [#:pre pre])  boolean?

  s : signal?
  pre : natural? = 0
Determines if s is present in the current instant when pre is 0.

If pre is larger than zero, returns whether or not s was present in previous instants. If pre is larger than the value of the pre-count-expr passed to esterel, an error is raised.

Examples:
> (define-signal S)
> (define-signal O1)
> (define-signal O2)
> (react! (esterel (if (present? S) (emit O1) (emit O2))))

'#hash((#<signal: S> . #f) (#<signal: O2> . #t))

> (define r
    (esterel
     #:pre 1
     (emit S)
     (pause)
     (if (present? S #:pre 1) (emit O1) (emit O2))))
> (react! r)

'#hash((#<signal: S> . #t))

> (react! r)

'#hash((#<signal: O1> . #t))

procedure

(signal-value s [#:pre n] #:can can)  any/c

  s : signal?
  n : natural? = 0
  can : (setof signal?)
Returns the value of s in the current instant if n is 0, unless it hasn’t been emitted, in which case it returns the value in the previous instant.

If n is larger than zero, then signal-value returns the value of s is the nth previous instant. If n is larger than the value of the pre-count-expr passed to esterel, an error is raised. If the value has never been emitted and the signals declaration did not have an #:init clause, an error is raised.

The can argument indicates which signals can be emitted by the remaining computation and it must be supplied if if n is 0. That is, if it is possible that some signal can be emitted in the current instant after signal-value returns, then that signal must be in the set can.

Examples:
> (define-signal
    S1 #:combine + S2 #:combine +
    O1 #:combine + O2 #:combine + O3 #:combine +)
> (define r
    (esterel
     #:pre 1
     (emit S1 2)
     (emit S1 3)
     (emit S2 0)
     (pause)
     (emit S2 6)
     (emit O1 (signal-value S1 #:can (set O2 O3)))
     (emit O2 (signal-value S2 #:pre 1))
     (emit O3 (signal-value S2 #:can (set)))))
> (react! r)

'#hash((#<signal: S1> . 5) (#<signal: S2> . 0))

> (react! r)

'#hash((#<signal: O1> . 5)

       (#<signal: S2> . 6)

       (#<signal: O2> . 0)

       (#<signal: O3> . 6))

procedure

(emit s)  void?

  s : (signal?)
(emit s v)  void?
  s : (signal?)
  v : any/c
Emits s. If one argument is passed, then s must not be a value-carrying signal. If two arguments are passed then s must be a value-carrying signal and the value v is emitted.

3.3 Control Operations🔗ℹ

syntax

(par expr ...)

Executes each expr in parallel, waiting for all of them to complete. The result is a set of all of the values of the exprs.

procedure

(pause)  void?

Pauses the current thread; when all of the threads are paused (or they are canceled via exit-trap, the instant is over. During the next instant, control picks up wherever the pause was.

In this example, we pause in the middle of the loop and the value of n is carried forward from instant to instant.

Examples:
> (define-signal S1 #:combine +)
> (define r
    (esterel
     (let loop ([n 0])
       (when (even? n)
         (emit S1 n))
       (pause)
       (loop (+ n 1)))))
> (for/list ([i (in-range 5)])
    (react! r))

'(#hash((#<signal: S1> . 0))

  #hash()

  #hash((#<signal: S1> . 2))

  #hash()

  #hash((#<signal: S1> . 4)))

syntax

(suspend body-expr when-expr)

When resuming from a pause in body-expr, suspends body-expr when when-expr returns a true value. The suspensions also affect any with-signal forms in (the dynamic extent of) when-expr.

In the second instant in this example, S1 is not emitted because that thread is suspended. In the third instant, however it is, because S2 is not emitted.

> (define-signal S1 S2)
> (define r
    (esterel
     (par
      (begin (pause)
             (emit S2)
             (pause))
      (suspend
       (let loop ()
         (pause)
         (emit S1)
         (loop))
       (present? S2)))))
> (react! r)

'#hash()

> (react! r)

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

> (react! r)

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

> (react! r)

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

Signals whose declaration are suspended do not see the instants that happen during the suspension. Specifically, the pre values are not affected by instants that are unseen because of the suspension, as this example demonstrates.
> (define-signal
    O #:init (set) #:combine set-union
    S2)
> (define r
    (esterel
     #:pre 1
     (suspend
      (with-signal (S1)
        (let loop ()
          (emit S1)
          (pause)
          (emit O (present? S1 #:pre 1))
          (loop)))
      (present? S2))))
> (react! r)

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

> (react! r #:emit (list S2))

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

> (react! r)

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

In the second instant, S1 is not emitted but, because its declaration is suspended, the present test on the previous instant’s version of S1 is true and thus the result of react! has O mapped to #t.

syntax

(with-trap trap-id body-expr ...)

Binds trap-id to a newly created trap and evaluates the body-exprs. If the trap bound to trap-id is passed to exit-trap, the computation in the rest of the body-exprs is skipped and the result of the with-trap is the value passed to exit-trap.

In this example, the (exit-trap t) causes the first instant to not have S2 emitted and causes the second instant to not have S1 emitted.

Examples:
> (define-signal S1 S2)
> (define r
    (esterel
     (with-trap t
       (par (begin
              (emit S1)
              (exit-trap t)
              (emit S2))
            (begin
              (pause)
              (emit S1))))))
> (react! r)

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

> (react! r)

'#hash()

procedure

(exit-trap t [v])  any/c

  t : trap?
  v : any/c = (void)
Exits to the trap t with the value v; does not return.

See also with-trap.

procedure

(trap? v)  boolean?

  v : any/c
Recognizes values bound by with-trap.

syntax

(exec esterel-id
      ([id id-expr] ...)
      exec-expr ...
      maybe-kill-exprs
      maybe-suspend-exprs
      maybe-resume-exprs)
 
maybe-kill-exprs = 
  | #:kill kill-expr ...
     
maybe-suspend-exprs = 
  | #:suspend suspend-expr ...
     
maybe-resume-exprs = 
  | #:resume resume-expr ...
Creates a separate thread to evaluate the exec-exprs. Meanwhile, pauses until the first instant after the exec-exprs terminate.

The id-expr expressions are evaluated when the exec is evaluated and they are bound to the ids, whose scope spans the exec-exprs, kill-exprs, suspend-exprs, and the resume-exprs.

The suspend-exprs and resume-exprs are evaluated each instant that the exec is suspended or resumed, with the caveat that, if the exec is suspended for multiple instants, the suspend-exprs are evaluated only during the first instant when the expr is suspended. Similarly, when the exec is resumed, the resume-exprs are evaluated only once, until the next time the exec is resumed (following some future suspension).

If the exec is terminated (because a parallel thread exit-traps to an enclosing with-trap) the kill-exprs are evaluated.

If they are evaluated, the kill-exprs, suspend-exprs, and resume-exprs are evaluated on the same thread as each other, and that thread is different from the thread evaluating the exec-exprs. Also, the kill-exprs, suspend-exprs, and resume-exprs must not raise errors or otherwise fail to return normally, as the overall esterel that they are running in will not function properly in that situation.

The intention of exec is to offer a controlled way to connect the synchronous computation (inside esterel) to the asynchronous computation that’s going on outside it. As such, the idea is that the exec-exprs may run some asynchronous computation, such as connecting to a website or waiting for a timeout and, when that completes, trigger another reaction with the results of the asynchronous computation communicated to the synchronous world via values on signals. That is, the last thing that the exec-exprss should do is trigger a reaction. But, that reaction should probably be triggered on the same thread that triggered the original reaction (or, at least, in a way that guarantees that only one reaction is running at a time). The syncronization required to establish that is left to the user of exec. One possibility, if a GUI is involved, is to use queue-callback in the exec-exprs, keeping every reaction on an eventspace’s handler thread.