Advertisement

ARx: Reactive Programming for Synchronous Connectors

Conference paper
  • 642 Downloads
Part of the Lecture Notes in Computer Science book series (LNCS, volume 12134)

Abstract

Reactive programming (RP) languages and Synchronous Coordination (SC) languages share the goal of orchestrating the execution of computational tasks, by imposing dependencies on their execution order and controlling how they share data. RP is often implemented as libraries for existing programming languages, lifting operations over values to operations over streams of values, and providing efficient solutions to manage how updates to such streams trigger reactions, i.e., the execution of dependent tasks. SC is often implemented as a standalone formalism to specify existing component-based architectures, used to analyse, verify, transform, or generate code. These two approaches target different audiences, and it is non-trivial to combine the programming style of RP with the expressive power of synchronous languages.

This paper proposes a lightweight programming language to describe component-based Architectures for Reactive systems, dubbed ARx, which blends concepts from RP and SC, mainly inspired to the Reo coordination language and its composition operation, and with tailored constructs for reactive programs such as the ones found in ReScala. ARx is enriched with a type system and with algebraic data types, and has a reactive semantics inspired in RP. We provide typical examples from both the RP and SC literature, illustrate how these can be captured by the proposed language, and describe a web-based prototype tool to edit, parse, and type check programs, and to animate their semantics.

1 Introduction

This paper combines ideas from reactive programming languages and from synchronous coordination languages into a new reactive language that both enriches the expressiveness of typical reactive programs and facilitates the usage of typical synchronous coordination languages.

Reactive programming languages, such as Yampa [14], ReScala [11], and Angular1, address how to lift traditional functions from concrete data values to streams of values. These face challenges such as triggering reactions when these streams are updated, while avoiding glitches in a concurrent setting (temporarily inconsistent results), distinguishing between continuous streams (always available) and discrete streams (publishing values at specific points in time), and avoiding the callback hell [15] resulting from abusing the observable patterns that masks interactions that are not explicit in the software architecture.

Synchronous coordination languages, such as Reo [2], Signal Flow Graphs [7], or Linda [9], address how to impose constraints over the interactions between software objects or components, restricting the order in which the interactions can occur, and where data should flow to. These face challenges such as how to balance the expressivity of the language—capturing, e.g., real-time [16], data predicates [18], and probabilities [3]—with the development of tools to implement, simulate, or verify these programs.

Both programs in Reactive Programming (RP) and Synchronous Coordination (SC) provide an architecture to reason about streams: how to receive incoming streams and produce new outgoing ones. They provide mechanisms to: (1) calculate values from continuous or discrete data streams, and (2) constraint the scheduling of parallel tasks. RP is typically more pragmatic, focused on extending existing languages with constructs that manage operations over streams, while making the programmer less aware of the stream concept. SC is typically more fundamental, focused on providing a declarative software layer that does not address data computation, but describes instead constraints over interactions that can be formally analysed and used to generate code.

This paper provides a blend of both worlds, by proposing a language—ARx—with a syntactic structure based on reactive programs, and with a semantics that captures the synchronisation aspects of synchronous coordination programs. This paper starts by providing a better context overview of reactive and synchronous programs (Sect. 2). It then introduces the toolset supporting ARx in Sect. 3, available both to use as a web-service2 or to download and run locally. The rest of the paper formalises the ARx language, without providing correctness results and focusing on the tools. It presents the core features of ARx in Sect. 4, introducing an intermediate language to give semantics to ARx of so-called stream-builders and providing a compositional encoding of ARx into stream-builders. Two extensions to ARx are then presented. The first consists of algebraic data types, in Sect. 5, making the data values more concrete. The second, in Sect. 6, enriches the syntax of ARx and of stream-builders, and introduces new rules to the operational semantics, to support the notion of reactivity.

2 Overview over Reactive and Synchronous Programs

This section selects a few representative examples of reactive programs and of synchronous coordinators. It uses a graphical notation to describe these programs, partially borrowed from Drechsler et al. [11], and explains the core challenges addressed by both approaches.

Reactive Programs. Figure 1 includes 3 examples of reactive programs:

 
(top-left)

A simple arithmetic computation, used by Bainomugisha et al. [5], with the program “\(c = a\times b; d=a+c\)” using reactive variables.

(right)

A controller of a fan switch for embedded systems, used by Sakurai et al. [19], with the program “Open image in new window”.

(bottom-left)

A GUI manager that selects which information to display, either from the continuous stream of mouse coordinates, or from the continuous stream of current time, with the program ‘Open image in new window”.

 
Fig. 1.

Example of typical Reactive Programs.

Consider the arithmetic computation example. It has 4 (reactive) variables, abcd, and the sources (depicted as triangles) may fire a new value. Firing a new value triggers computations connected by arrows; e.g., if b fires 5, the Open image in new window operation will try to recompute a new product, and will update c, which in turn will fire its new value. So-called glitches can occur, for example, if a fires a value, and Open image in new window is calculated with the old value of c (before Open image in new window updates its value). Different techniques exist to avoid glitches, by either enforcing a scheduling of the tasks, or, in a distributed setting, by including extra information on the messages used to detect missing dependencies. Languages that support reactive programming often include operations to fire a variable (e.g., Open image in new window in ReScala), to react to a variable update (e.g., Open image in new window in ReScala), to ask for a value to be updated and read (e.g., Open image in new window in ReScala), and to read or update a value without triggering computations. Hence the effort is in managing the execution of a set of tasks, while buffering intermediate results, and propagate updates triggered by new data.

Consider now the fan controller. It includes a loop with dashed arrows, capturing the variable \( fan@last \), i.e., the previous value of \( fan \). This is a solution to handle loops, which are either forbidden or troublesome in RP. Consequently, the system must know the initial value of \( fan \) using a dedicated annotation.

Finally, consider the GUI example. This includes dashed triangles, which denote continuous streams of data (often refer to as behaviour in functional RP, as opposed to signal). This means that updates to the mouse coordinates or to the time passing do not trigger a computation. Here \( sel \) can fire a boolean that will trigger data to flow from either \( mouse \) or \( time \) to \( disp \). Furthermore, the computation may not depend on all of its inputs, as opposed to the other operations seen so far. Hence, the composing operation depends, at each phase, on either \( mouse \) or \( time \), and not on both.

Synchronous Coordinators. Synchronous coordinators provide a finer control over the scheduling restrictions of each of the stream updates, as illustrated in the two examples of Fig. 2. These represent different coordinators that have two inputs, a and b, and alternate their values to an output stream o. In RP a similar behaviour could be captured by “Open image in new window”. Using a synchronous coordinator, one can exploit synchrony and better control the communication protocol.
Fig. 2.

Example of typical Synchronous Coordinators: variations of an alternator.

The coordinators of Fig. 2 use the blocks Open image in new window , and Open image in new window , and may connect streams directly. Unlike RP, these connections are synchronous, meaning that all streams involved in an operation must occur atomically. E.g., each stream can fire a single message only if the connected block or stream is ready to fire, which in turn can only happen if all their outputs are ready to fire. In the left coordinator, the top a can output a message only if it can send it to both o and the Open image in new window . This Open image in new window blocks a or b unless both a and b can fire atomically. The Open image in new window can buffer at most one value, blocking incoming messages when full. The left coordinator receives each data message from both a and b, sending the message from a to c atomically and buffering the value from b; later the buffered message is sent to c, and only then streams a and b can fire again. The right coordinator uses a Open image in new window that alternates between blocking a and blocking b, and it buffers the value temporarily to avoid o from having to synchronise with a or b.

Remarks. In SC data streams can fire only once, and do not store this value unless it is explicit in the coordinator. In RP, when a stream fires a value, this value is stored for later reuse – either by the sender or by the computing tasks, depending on the implementation engine. Also, the notion of synchronisation, describing sets of operations that occur atomically, is not common in RP, since RP targets efficient implementations of tasks that run independently.

The term reactive has also been applied in the context of reactive systems and functional reactive systems. The former addresses systems that react to incoming stimuli and are responsive, resilient, elastic and message driven, described in the Reactive Manifesto [1]. The latter is a specific take on reactive programming based on functions over streams that distinguish (continuous) behaviour from (discrete) events [12]. Early work on synchronous languages for (real-time) reactive systems has been focused on safety-critical reactive control system, and includes synchronous programming and (synchronous) dataflow programming [5]. Similarly to synchronous coordination, synchronous languages such as Esterel [6] and StateCharts [13], assume that reactions are atomic and take no time, simplifying programs and allowing their representation as finite state machines which can be later translated into sequential programs.
Fig. 3.

Screenshot of the widgets in the online tool for ARx programs.

3 ARx Toolset

We implemented an open-source web-based prototype tool to edit, parse, and type check ARx programs, and to animate their semantics.3 These tools are developed in Scala, which is compiled to JavaScript using ScalaJS.4 This section starts by giving a quick overview on how to use the tools, using as running example a version of the GUI manager from Fig. 1 in ARx. The toolset includes several widgets, depicted in Fig. 3: Open image in new window the editor to specify the program, Open image in new window the architectural view of the program, Open image in new window the type and semantic analysis of the program, Open image in new window a finite automaton capturing the reactive semantics of the program, and Open image in new window a set of predefined examples.

Most of the syntax in Open image in new window in introduced in Sect. 4. Variables, such as mouse and time, denote streams of data; line breaks are ignored; and the statements “ Open image in new window ” mean that the stream display merges the data from mouse and display. Extensions to the core syntax include (1) algebraic data types (Sect. 5), deconstructed with match (line 5), and (2) a reactive variable introduced by the arrow Open image in new window in line 4 (Sect. 6).

The semantics of an ARx program is given by a guarded command language, which we call stream builders (Sect. 4.2), following the ideas from Dokter and Arbab’s stream constraints [10]. An instance of this intermediate language is illustrated in Open image in new window , and includes not only stream variables from ARx, but also memory variables (e.g., Open image in new window ). Guarded commands include the guards (1) get(mouse) to denote a destructive read from the mouse stream, (2) Open image in new window as a predicate introduced in our first extension, and Open image in new window is a non-destructive read introduced in our second extension.

Stream builders have an operational semantics: they evolve by consuming input streams and memory variables, and by writing to output streams. Furthermore, the reactive extension in Sect. 6 adds an extra step to signal the interest in writing-to or reading-from a stream. This reactive semantics is animated in an automata view, depicted in Open image in new window . Note that this automata grows quickly, but it is usually unnecessary, as the stream builders act as a compact and symbolic representation of the automata.

4 Core ARx

4.1 ARx: Syntax

A program is a statement, according to the syntax in Fig. 4. Expressions are either terms t, or names of so-called stream builders bn parameterised by a sequence of variables \(\overline{x}\). In turn, terms can be stream variables x, data values d, or function names parameterised by variables \(\overline{x}\).

So far we leave open the notions of stream builders and functions. Stream builders will be introduced in Sect. 4.2, and will give semantics to ARx programs. Functions are assumed to be deterministic and total with an interpretation \(\mathcal {I}\) that maps closed terms to values; in Sect. 5 we will restrict to constructors of user-defined algebraic data types, as in our prototype implementation.

Regarding the remaining constructions, a statement is either an assignment a, a stream expression e, a builder definition d, or a parallel composition of statements s s. An assignment assigns a stream expression e to a non-empty sequence of stream variables \(\overline{x}\). A builder definition Open image in new window introduces a name bn associated to a new stream builder of a given block of statements s.
Fig. 4.

ARx’ basic syntax, where \( bn \) ranges over names of stream builders, \( fn \) ranges over names of functions, and x over stream variables.

Examples. The examples below assume the existence of stream builders Open image in new window and Open image in new window ,5 and the function Open image in new window with some interpretation. Consider the Open image in new window definition, capturing the program from the left of Fig. 2. This has two input streams as parameters: Open image in new window and Open image in new window , which must fire together because of the Open image in new window . Their values are redirected to Open image in new window : the one from Open image in new window flows atomically, and the one from Open image in new window is buffered until a follow-up step. The stream Open image in new window is the only output of the definition block.

Stream builders are typed, indicating the data types that populate the each input and output stream. Our implementation uses a type-inference engine that unifies matching streams and uses type variables. In our example from Fig. 3, the inferred type of the Open image in new window builder is Open image in new window, meaning that its first argument has type Open image in new window , and all other ports must have the same type Open image in new window . The type system also imposes all input stream variables inside Open image in new window clauses to be parameters. We leave the type rules out of the scope of this paper, which focuses on the tools and on the semantics of ARx.

4.2 Stream Builders: Syntax

Programs in ARx express functions from streams to streams, and describe the (non-strict) order between consuming and producing values. ARx’s semantics is given by stream builders, defined below, which are closely inspired on Dokter and Arbab’s stream constraints [10].

Definition 1

(Stream builder). A stream builder sb follows the grammar:

where v is a variable name, t is a term, and \(\overline{x}\) is a sequence of elements from x.

Each variable v can represent an input stream or an output stream (or both), as in ARx, or internal memory. Terms t are the same as in ARx, but with variables also over the internal memory, and we write \(\textsf {fv}(t)\) to denote the set variables in t. Let s be a variable pointing to a stream and m a memory variable. Intuitively, a guard \(\mathsf {get} (s)\) means that the head of s, denoted by s(0), is ready to be read; a guard \(\mathsf {get} (m)\) means that the memory variable m is defined; and a guard \(\mathsf {und} (x)\) means that x has no value yet or is undefined.

A stream builder consists of an initial update that initialises memory variables and a set of guarded commands of the form \(g \rightarrow u\) that specify a set of non-deterministic rules, such that the update u can be executed whenever guard g holds. When executing an update from a guarded command with a guard that succeeds, each \(s:=t\) sets s(0) to the value obtained from t, each \(m:=t\) sets m to be the value from t; every stream s with \(\mathsf {get} (s)\) in the guard is updated to \(s'\) (the head of s is consumed), and every memory m with \(\mathsf {get} (m)\) in the guard—and not set in the update—becomes undefined. As a side-remark, these constructions \(\mathsf {get}\), \(\mathsf {und}\), and \(v:=t\) are analogue to the operations get, nask, and tell, respectively, over shared tuple-spaces in Linda-like languages, well studied in the literature in that context [8].

We further restrict which streams can be used in updates and guards based on whether they are input streams I—that can be read—or output streams O—that can be built. This is formalised by the notion of well-definedness below. Intuitively, in addition to the previous restrictions, the initial updates can be only over memory variables and use terms with no variables, i.e., memory variables can only be initialized with data values rather than streams; and for every guarded command, undefined variables cannot be “gotten”, both guards and terms in updates can only use defined memory and input stream variables, and assignments in guarded commands cannot have cycles (which can occur since input and output stream variables may intersect). We use the following notation: given a stream builder sb, we write \(sb{.}I \), \(sb{.}O \), and \(sb{.}M \) to denote its input streams, output streams, and memory variables; and we write \(\mathsf {get} (\overline{v})\) (and analogously for \(\mathsf {und} \)) to denote a set of occurrences of \(\mathsf {get} (v)\), for every \(v \in \overline{v}\).

Definition 2

(Well-defined stream builder). Let \( sb \) be the stream builder:
$$\overline{v_{init} := t_{init}} \wedge [\mathsf {get} (\overline{v_{ get }}), \mathsf {und} (\overline{v_{ und }}), \overline{t_{ guard }} \rightarrow \overline{v_{out} := t_{out}}, \ldots ]. $$
We say \( sb \) is well-defined if \(\overline{v_{init}} \subseteq sb{.}M \) and \(\textsf {fv}(t_{init})= \emptyset \), and if for every guarded command, the following conditions are met:
$$\begin{aligned} \overline{v_{get}}\cap \overline{v_{und}} =~&\emptyset&\overline{v_{get}}\cup \overline{v_{und}}\subseteq ~&sb{.}I \cup sb{.}M&\overline{v_{out}}\subseteq ~&sb{.}O \cup sb{.}M \\ \textsf {fv}(t_{guard}) \subseteq ~&\overline{v_{get}}&\textsf {fv}(\overline{t_{out}}) \times \overline{v_{out}}~&\text {is acyclic}&\textsf {fv}(t_{out}) \subseteq ~&\overline{v_{get}} \end{aligned}$$
Examples. We omit the initial updates of a stream builder when empty, and write Open image in new window in the examples below to define a stream builder Open image in new window as \(\overline{upd}\wedge \overline{gc}\) over variables \(\left\{ v_1,v_2,\ldots \right\} \), using the convention that i denotes an input stream, o denotes an output stream, and m denotes a memory.

Informally, the \( sb_{add} \) stream builder receives values from two streams and outputs their sum. At each round, it atomically collects a value from each input stream and produces a new output value. In \( sb_{xor} \) there are two non-deterministic options at each round: to send data to one output stream or to a second output stream. In \( sb_{fifo} \) the two options are disjoint: if m is undefined only the left rule can be triggered, and if m is defined only the right rule can be triggered, effectively buffering a value when m is undefined, and sending m when m is defined (becoming undefined again). The formal behaviour is described below. Later in the paper, we will present a composition operator for stream builders, allowing \( sb_{alternator} \) to be built out of simpler builders.

4.3 Stream Builders: Operational Semantics

Consider a stream builder \(sb = \overline{init} \wedge \overline{gc}\) with an interpretation \(\mathcal {I} \) of closed terms as data values. The semantics of sb is given by a rewriting rule over the state of a stream builder. This state consists of a map \(\sigma _m\) that assigns memory variables to their data values, and is initially set to \(\langle \overline{init} \rangle \). We use the following notation: \(t[\sigma ]\) captures the substitution on t of \(\sigma \), \(\textsf {dom}(\sigma )\) is the domain of \(\sigma \), \(\sigma -X\) is the map \(\sigma \) excluding the keys in X, \(\sigma _1 \cup \sigma _2\) is the map \(\sigma _1\) extended with \(\sigma _2\), updating existing entries, and \(\mathsf {gets}(g)\) returns the variables within \(\mathsf {get}\) constructs in the guard g. We use \(\sigma _i\) and \(\sigma _o\) to represents the current mapping from (the first element of) input and output stream variables to data values, respectively.

Definition 3

(Guard satisfaction). Given a guard g and the current state of a system, given by \(\sigma _m\) and \(\sigma _i\), the satisfaction of g, denoted \(\sigma _m,\sigma _i \models g\), is defined as follows.

Definition 4

(Operational semantics). The semantics of a stream builder \(sb = \overline{init} \wedge \overline{gc}\) is given by the rule below, with an initial configuration \(\langle \overline{init} \rangle \).
Intuitively, \(\langle \sigma \rangle \xrightarrow { {\sigma _i,\sigma _o}} \langle \sigma ' \rangle \) means that, for every variable i and data value d such that \(\sigma (i)=d\), the state evolves by reading the value d from the head of the stream i, and by adding a value to each stream \(o \in \textsf {dom}(\sigma _o)\), given by \(\sigma _o(o)\). The internal state is captured by \(\sigma _m\) that stores values of memory variables in \(sb{.}M \), which is updated based on \(\sigma _i\). Furthermore, the system can only evolve by executing an active guarded command. Intuitively, a guarded command is active if the current state of memory (\(\sigma _m\)) and input stream (\(\sigma _i\)) variables satisfy the guard g, such that: each term guard has an interpretation that evaluates to true; all required input stream variables coincide with the set of defined input stream variables; all required memory variables are contained in the defined memory variables; and all required undefined memory variables are indeed undefined. For example, the following are valid runs of the stream builders of Sect. 4.2.

4.4 Composing Stream Builders

The composition of two stream builders yields a new stream builder that merges their initial update and their guarded commands, under some restrictions. I.e., the memory variables must be disjoint, some guarded commands can be included, and some pairs of guarded commands from each stream builder can be combined. This is formalised below in Definitions 5 and 6 after introducing some preliminary concepts. In the following we use the following auxiliary functions: Open image in new window returns the output streams assigned in the RHS of gc, Open image in new window returns the input streams inside Open image in new window statements of gc, and Open image in new window returns Open image in new window.

The composition of stream builders follows the same principles as the composition of, e.g., constraint automata (CA) [4]. But unlike CA and most Reo semantics, it supports explicit many-to-many composition. I.e., a builder with an input stream i can be composed with another builder with the same input stream i, preventing individual guarded commands from each builder to use i without involving the other stream builder. Similarly, a builder with an output stream o can be combined with another one with the same output stream o, although only one builder can write to o at a time. A builder with an input stream x can be composed with another with an output stream with the same name x, making x both an input and an output in further compositions. The composition rules were carefully designed to keep the composition commutative and associative, which we do not prove in this paper.

We introducing the following auxiliary predicates used in the composition, using \(gc_i\) to range over guarded commands and \(I_1\) to range over stream variables (meant to be from the same stream builder as \(gc_1\)).The predicate Open image in new window means that any input stream in \(I_1\) that is an output of \(gc_2\) must be read by \(gc_1\), i.e., must be an input stream used by \(gc_1\). Its dual Open image in new window is not symmetric: it means that any input stream in \(I_1\) that is an input \(gc_2\) must either be written-to or read-by \(gc_1\). This reflects the fact that input streams replicate data, and that input streams may also be output streams that could be used to synchronise. The predicate Open image in new window states that \(gc_1\) and \(gc_2\) do not share output streams, reflecting the fact that only one rule can write to a stream at a time. The last predicate Open image in new window states that \(gc_2\) will not affect any of the input streams in \(I_1\). Intuitively this means that \(gc_2\) may read-from or write-to streams from another builder \(sb_1\) if they can also be written by \(sb_1\), but not if they are read by \(sb_1\).

The composition of guarded commands and of stream builders is defined below, based on these predicates.

Definition 5

(Composition of guarded commands (\(gc_1\bowtie gc_2\))). For \(i\in \{1,2\}\), let \(gc_i = \mathsf {get} (\overline{v_{gi}}),\mathsf {und} (\overline{v_{ui}}),\overline{t_{gi}} \rightarrow \overline{v_{oi} := t_{oi}}\) be two guarded commands. Their composition yields \(gc_1 \bowtie gc_2\) defined below.
$$\begin{aligned}&\mathsf {get} ((\overline{v_{g1}}\cup \overline{v_{g2}}) - (\overline{v_{o1}}\cup \overline{v_{o2}}))\,,\, \mathsf {und} (\overline{v_{u1}}\cup \overline{v_{u2}})\,,\, \overline{t_{g1}}\cup \overline{t_{g2}} \,\rightarrow ~ \overline{v_{o1} \,{:=}\, t_{o1}} \cup \overline{v_{o2} \,{:=}\, t_{o2}} \end{aligned}$$

Definition 6

(Composition of stream builders (\(sb_1\bowtie sb_2\))). For \(i\in \{1,2\}\), let \(sb_i = \overline{init_i} \wedge [\overline{gc_i}]\) be two stream builders. Their composition yields \(sb = sb_1 \bowtie sb_2 = (\overline{init_1} \cup \overline{init_2}) \wedge [gcs]\), where \(sb{.}O =sb_1{.}O \,\cup \, sb_2{.}O \), \(sb{.}I =sb_1{.}I \,\cup \, sb_2{.}I \), \(sb{.}M =sb_1{.}M \,\cup \, sb_2{.}M \), and \( gcs \) is given by the smallest set of guarded commands that obeys the rules below, which are not exclusive.

Intuitively, any guarded command can go alone, unless it must synchronise on shared streams. Any two guarded commands can go together if their synchronization is well-defined and do not perform behaviour that must be an exclusive choice. Observe that the composition of two well-defined stream builders (c.f. Definition 2) may not produce a well-defined stream builder (e.g. cyclic assignments), in which case we say that the stream builders are incompatible and cannot be composed.

Example. The sequential composition of two Open image in new window builders is presented below, annotated with the rule name that produced it. The Open image in new window guard was dropped during composition (Definition 5), but included here to help understanding. The last two guarded commands, in Open image in new window , denote scenarios where the middle stream b remains open for synchronization. These are needed to make the composition operator associative, but can be discarded when hiding the internal streams like b. This is not explained here, but is implemented in our prototype tool. Following a similar reasoning, the stream builder Open image in new window can be produced by composing the stream builders Open image in new window, Open image in new window, and Open image in new window, which has no internal streams.

4.5 ARx’s Semantics: Encoding into Stream Builders

Fig. 5.

Semantics: encoding of statements of ARx as a stream builder.

A statement in ARx can be encoded as a single stream builder under a context \(\varGamma \) of existing stream builders. More precisely, \(\varGamma \) maps names of stream builders bn to triples \((sb,\overline{x_I},\overline{x_O})\) of a stream builder sb, a sequence of variables for input streams \(\overline{x_I}\) of sb, and a sequence of variables for output streams \(\overline{x_O}\). Given a statement s and a context \(\varGamma \), we define the encoding of s as Open image in new window, defined in Fig. 5. Evaluating Open image in new window results in a pair \((sb,\overline{x_O})\) containing the encoded stream buffer and a sequence of variables. This sequence captures the output stream variables of sb. In the encoding definition we write [x/y] to mean that y substitutes x. Our implementation further applies a simplification of guarded commands in the Open image in new window clause, by hiding output streams not in \(\overline{x_O}\) and guarded commands that consume streams that are both input and output; but we do omit this process in this paper.

The composition exemplified in Sect. 4.4, regarding the alternator, is used when calculating the encoding of “ Open image in new window ” below, where Open image in new window.

5 Extension I: Algebraic Data Types

This section extends our language of stream builders with constructs for algebraic data types, allowing types to influence the semantics. The grammar, presented in Fig. 6, extends the grammar from Fig. 4 with declarations of Algebraic Data Types (ADTs), Open image in new window and Open image in new window primitive stream builders, and type annotations for builder definitions. For simplicity, we use the following notation: we omit Open image in new window, Open image in new window, and Open image in new window when Open image in new window is empty; we write Open image in new window, Open image in new window, and \( bn (x)\) instead of Open image in new window, Open image in new window, and \( bn (x:\alpha )\), respectively, when \(\alpha \) is a type variable not used anywhere else; and we omit the output type Open image in new window in builder definitions to denote a sequence of fresh type variables, whose dimension is determined during type-checking (when unifying types).
Fig. 6.

Syntax: extending the syntax from Fig. 4 with ADTs, where \(\alpha \) ranges over type variables, D over type names, and Q over data constructors.

A program starts by a list of definitions of algebraic data types, such as the ones below, which we will assume to be included in the header of all programs.
These ADTs are interpreted as the smallest fix-point of the underlying functor, i.e., they describe finite terms using the constructors for data types. All constructors Q must have at least one argument, but we write Q without arguments to denote either \(Q(\textsf {Unit})\) or \(Q(\textsf {U})\). Each definition of an ADT Open image in new window, e.g., Open image in new window, introduces:
  • Term constructors \(Q_i\) to build new terms, e.g. Nil and \(\textsf {Cons} (\textsf {True},\textsf {Nil})\);

  • Term inspectors \(isQ_i(x)\) that check if x was built with \(Q_i\), e.g. \(is\textsf {Nil} \) and \(is\textsf {Cons} \) return True only if their argument has shape Nil or Cons, respectively;

  • Term projections \(getQ_{i,j}\) that given a term built with \(Q_i\) return the j-th argument, e.g. \(get\textsf {Cons} _2(\textsf {Cons} (\textsf {True},\textsf {Cons} (\textsf {False},\textsf {Nil}))) = \textsf {Cons} (\textsf {False},\textsf {Nil})\);

Fig. 7.

Semantics of match and build, considering that D is defined as Open image in new window.

Given these new constructs the new semantic encodings is presented in Fig. 7. For example, Open image in new window yields the builder below, and Open image in new window is undefined unless the type-inference can instantiate \(\alpha \) with a concrete ADT.

6 Extension II: Reactive Semantics

In reactive languages, produced data is typically kept in memory, possibly triggering consumers when it is initially produced. In this section we provide a finer control about who can trigger the computation, and a notion of memory that is read without being consumed. This will allow us to have memory variables that trigger computations, and others that do not.

In the semantics of stream builders we add a notion of active variables, whereas a guarded command can only be selected if one of its variables is active, and adapt the operational semantics accordingly. We also introduce a new element to the guards: \(\mathsf {ask} (v)\), that represents a non-destructive read.

Syntax: asking for data The extension for our language updates the grammar for assignments:
whose squiggly arrow is interpreted as a creation of a reactive variable: the values from e are buffered before being used by \(\overline{x}\), and this values can be read (non-destructively) when needed using the new guard \(\mathsf {ask} \). This is formalised below.

Observe that “\(\mathsf {get} (m) \rightarrow x:=m, m:=m\)” is very similar to “\(\mathsf {ask} (m) \rightarrow x:=m\)”. The former consumes the variable m and defines it with its old value, and the latter reads m without consuming it. This subtle difference has an impact in our updated semantics, defined below, by marking assigned variables as “active”. In the first case m becomes active, allowing guarded commands that use m to be fired in a follow up step. In the second case m will become inactive, and guarded commands using m with no other active variables will not be allowed to fire.

Semantics: Active/Passive Variables. The reactive semantics for a stream builder \(sb = \overline{init}\wedge \overline{gc}\) is given by the rules below. The state is extended with two sets of so-called active input and output variables, with initial state \(\langle \overline{init},\emptyset ,\emptyset \rangle \). A system can evolve in two ways: (1) by evolving the program as before, consuming and producing data over variables, or (2) by an update to the context that becomes ready to write to (push) or read from (pull) a stream. Below we write “out(u)” to return the assigned variables in u (c.f. Sect. 4.4), “in(g)” to return the variables of g within \(\mathsf {get}\) and ask constructs, and \(\langle \sigma \rangle \xrightarrow { {x}}_{g,u} \langle \sigma ' \rangle \) to denote the step from state \(\sigma \) to \(\sigma '\) by x when selecting the guarded command \(g\rightarrow u\).
The previous semantic rules must be accommodated to take the ask constructor into account. This is done by redefining the guard satisfaction definition in Sect. 4.3 to incorporate a new rule, presented below, and Open image in new window in Sect. 4.4 to include also the ask variables.

Example: ADT and Reactivity. We illustrate the encoding and semantics of reactive stream builders using the GUI manager example (Fig. 1 and Fig. 3). The equality below depicts the adapted system following the ARx syntax (left) and its semantics (right).

This encoding also returns the sequence of output streams, which in this case is Open image in new window . The stream builder is further simplified by our toolset by removing intermediate stream variables Open image in new window , Open image in new window , and Open image in new window from the updates, as depicted in the screenshot of Fig. 3- Open image in new window .

The following transitions are valid runs of this program.

7 Conclusions

We proposed ARx, a lightweight programming language to specify component-based architecture for reactive systems, blending principles from reactive programming and synchronous coordination languages. ARx supports algebraic data types and is equipped with a type checking engine (not introduced here) to check if streams are well-composed based on the data being streamed.

Programs are encoded into stream builders, which provide a formal and compositional semantics to build programs out of simpler ones. A stream builder specifies the initial state of a program and a set of guarded commands which describe the steps (commands) that the program can perform provided some conditions (guards)—over the internal state and the inputs received from the environment—are satisfied.

We built an online tool to specify, type check, and analyse the semantics of ARx programs, and visualize both the architectural view of the program and its operational reactive semantics.

Future work plans include the verification of properties, the addition of new semantic extensions, and the development of code generators. These properties could be specified using hierarchical dynamic logic and verified with model checkers such as mCRL2, following [17], or could address the possibility of infinite loops caused by priorities of push and pulls from the environment. The semantic extensions could target, e.g., notions of variability, probability, time, and quality of service.

Footnotes

Notes

Acknowledgment

This work was partially supported by National Funds through FCT/MCTES (Portuguese Foundation for Science and Technology), within the CISTER Research Unit (UIDB/04234/2020); by the Norte Portugal Regional Operational Programme (NORTE 2020) under the Portugal 2020 Partnership Agreement, through the European Regional Development Fund (ERDF) and also by national funds through the FCT, within project NORTE-01-0145-FEDER-028550 (REASSURE); and by the Operational Competitiveness Programme and Internationalization (COMPETE 2020) under the PT2020 Partnership Agreement, through the European Regional Development Fund (ERDF), and by national funds through the FCT, within projects POCI-01-0145-FEDER-029946 (DaVinci) and POCI-01-0145-FEDER-029119 (PReFECT).

References

  1. 1.
    The reactive manifesto v2.0 (2014). https://www.reactivemanifesto.org
  2. 2.
    Arbab, F.: Reo: a channel-based coordination model for component composition. Math. Struct. Comput. Sci. 14(3), 329–366 (2004)MathSciNetCrossRefGoogle Scholar
  3. 3.
    Baier, C.: Probabilistic models for Reo connector circuits. J. Univ. Comput. Sci. 11(10), 1718–1748 (2005)Google Scholar
  4. 4.
    Baier, C., Sirjani, M., Arbab, F., Rutten, J.J.M.M.: Modeling component connectors in Reo by constraint automata. Sci. Comput. Program. 61(2), 75–113 (2006)MathSciNetCrossRefGoogle Scholar
  5. 5.
    Bainomugisha, E., Carreton, A.L., Cutsem, T.V., Mostinckx, S., De Meuter, W.: A survey on reactive programming. ACM Comput. Surv. 45(4), 52:1–52:34 (2013)CrossRefGoogle Scholar
  6. 6.
    Berry, G.: The foundations of Esterel. In: Plotkin, G.D., Stirling, C., Tofte, M. (eds.) Proof, Language, and Interaction, pp. 425–454. The MIT Press (2000)Google Scholar
  7. 7.
    Bonchi, F., Sobocinski, P., Zanasi, F.: Full abstraction for signal flow graphs. In: Proceedings of the 42nd Annual Symposium on Principles of Programming Languages, POPL 2015, pp. 515–526. ACM, New York (2015)Google Scholar
  8. 8.
    Brogi, A., Jacquet, J.-M.: On the expressiveness of coordination via shared dataspaces. Sci. Comput. Program. 46(1–2), 71–98 (2003)MathSciNetCrossRefGoogle Scholar
  9. 9.
    Cridlig, R., Goubault, E.: Semantics and analysis of linda-based languages. In: Cousot, P., Falaschi, M., Filé, G., Rauzy, A. (eds.) WSA 1993. LNCS, vol. 724, pp. 72–86. Springer, Heidelberg (1993).  https://doi.org/10.1007/3-540-57264-3_30CrossRefGoogle Scholar
  10. 10.
    Dokter, K., Arbab, F.: Rule-based form for stream constraints. In: Di Marzo Serugendo, G., Loreti, M. (eds.) COORDINATION 2018. LNCS, vol. 10852, pp. 142–161. Springer, Cham (2018).  https://doi.org/10.1007/978-3-319-92408-3_6CrossRefGoogle Scholar
  11. 11.
    Drechsler, J., Salvaneschi, G., Mogk, R., Mezini, M.: Distributed REScala: an update algorithm for distributed reactive programming. In: Black, A.P., Millstein, T.D. (eds) Proceedings of the 2014 ACM International Conference on Object Oriented Programming Systems Languages & Applications, OOPSLA 2014, Part of SPLASH 2014, Portland, OR, USA, 20–24 October 2014, pp. 361–376. ACM (2014)Google Scholar
  12. 12.
    Elliott, C., Hudak, P.: Functional reactive animation. In: International Conference on Functional Programming (1997)Google Scholar
  13. 13.
    Harel, D.: Statecharts: a visual formalism for complex systems. Sci. Comput. Program. 8(3), 231–274 (1987)MathSciNetCrossRefGoogle Scholar
  14. 14.
    Hudak, P., Courtney, A., Nilsson, H., Peterson, J.: Arrows, robots, and functional reactive programming. In: Jeuring, J., Jones, S.L.P. (eds.) AFP 2002. LNCS, vol. 2638, pp. 159–187. Springer, Heidelberg (2003).  https://doi.org/10.1007/978-3-540-44833-4_6CrossRefGoogle Scholar
  15. 15.
    Maier, I., Rompf, T., Odersky, M.: Deprecating the observer pattern, p. 18 (2010)Google Scholar
  16. 16.
    Meng, S., Arbab, F.: On resource-sensitive timed component connectors. In: Bonsangue, M.M., Johnsen, E.B. (eds.) FMOODS 2007. LNCS, vol. 4468, pp. 301–316. Springer, Heidelberg (2007).  https://doi.org/10.1007/978-3-540-72952-5_19CrossRefGoogle Scholar
  17. 17.
    Hojjat, H., Massink, M. (eds.): FSEN 2019. LNCS, vol. 11761. Springer, Cham (2019).  https://doi.org/10.1007/978-3-030-31517-7CrossRefzbMATHGoogle Scholar
  18. 18.
    Proença, J., Clarke, D.: Interactive interaction constraints. In: De Nicola, R., Julien, C. (eds.) COORDINATION 2013. LNCS, vol. 7890, pp. 211–225. Springer, Heidelberg (2013).  https://doi.org/10.1007/978-3-642-38493-6_15CrossRefGoogle Scholar
  19. 19.
    Sakurai, Y., Watanabe, T.: Towards a statically scheduled parallel execution of an FRP language for embedded systems. In: Proceedings of the 6th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems, REBLS 2019, pp. 11–20. Association for Computing Machinery, New York (2019)Google Scholar

Copyright information

© IFIP International Federation for Information Processing 2020

Authors and Affiliations

  1. 1.CISTER, ISEPPortoPortugal
  2. 2.HASLab/INESC TECUniversidade do MinhoBragaPortugal

Personalised recommendations