Reactor:   a synchronous reactive language
1 Running Programs
prime
react!
2 Creating Processes
define-process
process
3 Defining Processes
pause&
par&
loop&
halt&
run&
3.1 Signals
define-signal
signal
emit&
present&
await&
await*&
last
last?
default
3.2 Control
suspend&
abort&
with-handlers&
4 Data
signal?
pure-signal?
value-signal?
signal/  c
signal=?
signal-name
reactor?
reactor-suspended?
reactor-done?
process?
reactor-safe?
5 Continuation Marks
reactor-continuation-marks
continuation-mark-set-tree->tree
continuation-mark-set-tree?
tree?
branch
leaf
6 Caveats and unstable API’s
6.1 Signals and Synchronization
6.2 Caveat concerning exception handling and control jumps
7.0

Reactor: a synchronous reactive language

Warning: This API is unstable, and may change without warning.

 (require reactor) package: reactor

Reactor is a synchronous reactive language in the style of ReactiveML. A program is represented by a reactor?.

The run of one program is broken up into reactions (also called instants), each of which can be though of as being instantaneousthat is, absent side effects, no concurrent thread of execution runs before or after any other thread. They all occur at the same time, and so seem to take zero logical time. Thus, concurrent computations are deterministic.

Every expression in the program either takes zero time (e.g. completes in the current reaction) or pauses, which "consumes" time, stopping the computation there until the next reaction.

The code within a reactor? can be a mix of both reactive code (like par&) and non-reactive code (e.g. normal racket expressions). This non-reactive will never consume time and should always terminate.

In general racket level side effects (mutation, non-termination, etc) may break the guarantee of determininistic concurrencuy.

1 Running Programs

procedure

(prime proc)  reactor?

  proc : process?
Create a new reactor?, who’s code is the body of proc.

procedure

(react! r start-signals ...)  any

  r : (and/c reactor? reactor-safe?)
  start-signals : (or/c pure-signal? (list/c value-signal? (listof any/c)))
Run one reaction in the r. The reaction begins by emit&ting the given signals with the given values.

2 Creating Processes

A process simply encapsulates code that may be run in a reactor?: That is to say it is the reactive analog of a function.

syntax

(define-process id body ...)

(define-process (id args ...) body ...)
The first variant defines a new process.

The second variant creates a new function, func which takes args and returns the process defined by body.

These processes are not related to Racket’s processes. Processes are also not related to Racket’s threads.

syntax

(process body ...)

Create a new process.

3 Defining Processes

A process may contain arbitrary racket code. In addition, it may use the use the following forms. By convention forms ending in a & may only be used inside of a process or define-process.

syntax

pause&

pause the current process until the next reaction. Evaluates to (void) in the next reaction.

Only valid inside of a process or define-process.

Examples:
> (define-process pause
    pause&
    (displayln 1))
> (define r (prime pause))
> (react! r)
> (react! r)

1

syntax

(par& e ...)

Runs each e concurrently. This expression completes evaluation when each new branch has finished. Evaluates to void.

At the end of a reaction if all but one branch has completed that branch becomes in tail position with respect to the par& form.

Only valid inside of a process or define-process.

Examples:
> (define-process par1
    (displayln (par& 1 2)))
> (react! (prime par1))

#<void>

> (define-process par2
    (displayln
     (par& 1
           (begin pause& 2))))
> (define r (prime par2))
> (react! r)
> (react! r)

2

syntax

(loop& body ...)

Loop bodys forever. The body of the loop must be non-instantaneous: it must pause each instant the loop (re)starts.

Only valid inside of a process or define-process.

Examples:
> (define-process loop
    (let ([i 0])
      (loop& (displayln i)
             (set! i (+ 1 i))
             pause&)))
> (define r (prime loop))
> (react! r)

0

> (react! r)

1

> (react! r)

2

> (react! r)

3

syntax

halt&

pause forever.

Only valid inside of a process or define-process.

Examples:
> (define-process halt
    (displayln 1)
    halt&
    (displayln 2))
> (define r (prime halt))
> (react! r)

1

> (react! r)
> (react! r)
> (define-process par-halt
    (par& (begin (displayln 1) pause& (displayln 2))
          (begin (displayln 3) halt& (displayln 4))))
> (define r2 (prime par-halt))
> (react! r2) ; note: these may display in either order, since printing is a side effect

3

1

> (react! r2)

2

> (react! r2)

syntax

(run& proc)

Start the given process within a reaction. That is, run& is the reactive analog of function application.

Only valid inside of a process or define-process.

Examples:
> (define-process (my-loop i)
    (displayln i)
    pause&
    (run& (my-loop (add1 i))))
> (define r (prime (my-loop 0)))
> (react! r)

0

> (react! r)

1

> (react! r)

2

3.1 Signals

Signals are the core communication mechanism both within a Reactor, and between a reactor and its environment. It is never safe to share a signal between two reactors.

Signals may be either present or absent within a given instant—present if the have been emit&ted during thie current reactor, and absent if it is the end of a reaction and it has never been emit&ted. This means that forms like present& and await& which look at the presence of a signal must delay their choice to the end of a reaction if the signal is to be absent.

syntax

(define-signal S)

(define-signal S default ...+)
(define-signal S default ...+ #:gather gather)
(define-signal S default ...+ #:gather gather #:contract contract)
Defines a new signal. The first variant defines a pure signal, with no value. They can only be present or absent The second and third variants define a value carrying signal, which may be emit&ed along side values. The default values on the signal will be default. Multiple emissions of the signal will be combined with gather which should be a associative procedure of twice as many arguments as the signal has values, and should return as many values. When the gather function is applied all of the values of one emissions with be supplied before the values of another, in order. If no gather function is provided an error is raised if the signal is emitted twice in the same instant. The value emitted on a signal can only be observed in the next instant. The value of a signal can be extracted via last, and forms like await& which dispatch on the carried value.

When contract is supplied the signal is protected by that contract. See also signal/c.

The defaut values, gather function, and contract may be supplied in any order.

syntax

(signal S e)

(signal (S ...) e)
(signal ([S default #:gather gather] ...) e)
Analogous to let, but for signals.

procedure

(emit& S)  void?

  S : pure-signal?
(emit& S v ...)  void?
  S : value-signal?
  v : any/c
Emits a signal in the current instant, making it present. If the signal carries values, they must be given.

Only valid inside of a process or define-process.

syntax

(present& S then else)

Evaluates to then if S is emit&ted in this instant. Evaluates to else in the next instant otherwise.

Only valid inside of a process or define-process.

syntax

(await& maybe-immediate maybe-count signal-expr)

(await& S [pattern body ...] ...+)
 
signal-expr = signal-or-list
  | (or signal-or-list ...+)
     
maybe-immediate = 
  | #:immediate
     
maybe-count = 
  | #:immediate n
 
  S : signal?
  signal-or-list : (or/c signal? (listof signal?))

syntax

(await*& S [(pattern ...) body ...] ...+)

 
  S : signal?
Awaits the emissions of the signal S. In the #:immediate variant, it may respond to the presence of S in the current instant. Otherswise it always pauses in the first instant. The first variant evaluates to (void).

If #:count is provided await& awaits that many emissions of S in as many instants.

If signal-expr is an or clause or a list of signals the await is triggered if any of the signals is present. However if #:count is provided, multiple of these signals being emit&ed counts as only one emission.

If pattern clauses are provided, S must be a value carrying signal. In this case the value is matched against the given patterns in the reaction after S is emitted. It evaluates to the body of the first match. If none match the form continues to await the signal. The await& form matches only signals that carry a single value. The await*& form can match many valued signals.

Only valid inside of a process or define-process.

procedure

(last S)  any

  S : value-signal?
Gets the values of S in the previous instant.

procedure

(last? S)  boolean?

  S : signal?
Was this signal emitted in the previous instant?

procedure

(default S)  any

  S : value-signal?
Gets the values that S was initialized with.

Examples:
> (define-signal input)
; pure-signal -> process
> (define-process (main input)
    (define-signal crosstalk 0 #:gather +)
    (par& (run& (counter input crosstalk))
          (run& (printloop crosstalk))))
; pure-signal? value-signal? -> process
> (define-process (counter input chan)
    (emit& chan 0)
    (loop&
     (await& #:immediate input)
     (emit& chan (add1 (last chan)))
     pause&))
; value-signal? integer -> process
> (define-process (printloop chan)
    (loop&
     (await& chan
             [times
              (printf "got total of ~a inputs\n" times)])))
> (define r (prime (main input)))
> (react! r)
> (react! r)

got total of 0 inputs

> (react! r input)
> (react! r)

got total of 1 inputs

> (react! r)
> (react! r input)
> (react! r)

got total of 2 inputs

3.2 Control

syntax

(suspend& e ... #:unless signal-expr)

 
signal-expr = signal-or-list
  | (or signal-or-list ...+)
 
  signal-or-list : (or/c signal? (listof signal?))
Runs e unless S any instant where signal-expr is emit&ted. Suspends the body, pauseing the computation otherwise. Evaluates to its the result of body.

If signal-expr is either an or clause or a list of signals then the suspend executes when any of the given signals are present.

Only valid inside of a process or define-process.

Examples:
> (define-process (hi unlock)
    (suspend&
     (loop& (displayln 'hello) pause&)
     #:unless unlock))
> (define-signal print)
> (define r (prime (hi print)))
> (react! r print)

hello

> (react! r)
> (react! r)
> (react! r print)

hello

> (reactor-suspended? r)

#t

syntax

(abort& e ... #:after S [pattern body ...] ...)

Runs the body until the signal S is emit&ted. If no pattern clauses are provided, the body is aborted in the next instant, and the form evaluates to (void).

If patterns are provided, they are matched against the value carried by S. The form evaluates to the body of the first clauses that matches. If non match the execution of e continues.

If e completes before S is emitted and the body is aborted, the form evaluates to the result of e.

Only valid inside of a process or define-process.

Examples:
> (define-process (annoying silence)
    (abort&
     (loop& (displayln "I know a song that gets on everybody's nerves") pause&)
     #:after silence))
> (define-signal off)
> (define r (prime (annoying off)))
> (react! r)

I know a song that gets on everybody's nerves

> (react! r)

I know a song that gets on everybody's nerves

> (react! r off)

I know a song that gets on everybody's nerves

> (react! r)
> (reactor-done? r)

#t

syntax

(with-handlers& body ... #:after-error [a b] ...)

Like with-handlers, but that works with reactive machines. Specifically, whenever a raised exception is caught by a with-handlers&, it behaves like an abort, with body being aborted at the end of the current instant, and the corresponding handler is run in the next instant.

If multiple errors are raised in the same instant they’re handlers are run in parallel, and the result of each thread is collected into a list (as with par&). The order of the list is not specified.

4 Data

procedure

(signal? S)  boolean?

  S : any
Is S a signal?

procedure

(pure-signal? S)  boolean?

  S : any
Is S a pure signal which carries no value?

procedure

(value-signal? S)  boolean?

  S : any

syntax

(signal/c c ...)

Creates a contract for value that contain c. The no argument case is equivalent to pure-signal?.

If form appears syntactically within #:contract option of the define-signal form the contract is checked when: values are emit&ted, values are read from the signal (e.g. via last), and when values are combined via the gather function. If the gather function violates its contract the positive party will be blamed.

If the contract is attached via another form the contract barrier does not cover the gather function. Instead the contract behaves like box or channel contracts, and is only checked when the value is read from or written to.

procedure

(signal=? s1 s2)  boolean?

  s1 : signal?
  s2 : signal?
Are these the same signal? True if emit&ting either signal would cause the other signal to be present.

procedure

(signal-name s)  (and/c symbol? (not/c symbol-interned?))

  s : signal?
Gets a symbol who’s string value is the name this signal was first created with, and which is eq? to the signal-name of another signal only if the two signals are signal=?.

procedure

(reactor? r)  boolean?

  r : any
Is r a reactor?

procedure

(reactor-suspended? r)  boolean?

  r : reactor?
Is r completely suspended. That is, the reaction will immediatly pause making no progress unless a signal is provided which will cause a suspend& to execute its body, or a await& to make progress, etc.

procedure

(reactor-done? r)  boolean?

  r : reactor?
Is r done. That is the process which which r was created has completed.

procedure

(process? p)  boolean?

  p : any
Is p a process?

procedure

(reactor-safe? r)  boolean?

  r : reactor?
Can react! be called directly on this reactor? It returns false if control escapes a reaction via an abort, exception or other control jump, or if a reaction is already running in a different thread.

This check is not thread safe.

5 Continuation Marks

Reactor provides the ability to get the current continuation marks from a paused reactor?. However continuation marks in reactor have fundamental difference from those in racket: They are a tree rather than a list. This is because par& essentually forks the current continuation into several branches. Therefor Reactor mimics the racket continuation marks API, but extends it with trees.

procedure

(reactor-continuation-marks r)  continuation-mark-set-tree?

  r : (and/c reactor? reactor-safe?)
Gets the continuation marks for r. This is the Reactor equivalent of continuation-marks.

procedure

(continuation-mark-set-tree->tree cmst key)  tree?

  cmst : continuation-mark-set-tree?
  key : any/c
Get a tree containing the marks for key. The Reactor analog of continuation-mark-set->list.

procedure

(continuation-mark-set-tree? it)  boolean?

  it : any/c
Is it an continuation mark set tree, the representation of a the continuation marks from a reactor?

procedure

(tree? it)  boolean?

  it : any/c

struct

(struct branch (values children)
    #:transparent)
  values : list?
  children : tree?

struct

(struct leaf (values)
    #:transparent)
  values : list?
The representation of a tree of continuation marks from a reactor?. tree? returns true for branches and leafs. This representation of marks is "top down": that is the first mark is the mark at the top of the continuation tree, and therefor the oldest mark. The is the opposet of racket’s continuation mark lists, where the first value is the most recent mark.

A reactor without active par&s will always be represented by a leaf. A reactor with an par& with have a branch. The branch-values will contain the continuation mark values from above the par&. branch-children will contain a tree for each active branch of the par.

Note that this mean there will always be more than one child of a branch: If a par& has a single branch at the end of a reaction that branch will become in tail position w.r.t the enclosing context of the par&, removing the par& itself.

6 Caveats and unstable API’s

Warning: The API’s and behaviors presented here are especially unstable, and may change without warning.

6.1 Signals and Synchronization

Signals act as synchronizable event, which becomes ready for synchronization at the end of a reaction in which the signal was emit&ted. The synchronization result is the signal itself.

Signals are currently not thread safe: if a signal is used (via last? or last) in a thread different from a reaction where it is being used, extra synchronization must be used to ensure the signal is not look at during a reaction.

6.2 Caveat concerning exception handling and control jumps

Catching an exception using with-handlers or call-with-exception-handler, capturing and and applying a continuation inside of a reaction, or aborting to a prompt inside of a reaction is unsafe if any of these cross a continuation containing any reactive form and do not jump completely outside of the reaction. For example, if an exception passed through a par&, suspend&, or abort& the reactors control structure may become corrupted, and the reaction behavior and the state of its signals is undefined. Control may safely leave the reactor in this way, but the reactor is marked as unsafe.

However, Catching exceptions with with-handlers& is safe.