Advanced Join-Patterns for the Actor Model based on CEP Techniques

Humberto Rodríguez Avila, Joeri De Koster, Wolfgang De Meuter

Technical Report

October 26, 2020

Vrije Universiteit Brussel Faculty of Sciences and Bio-engineering Sciences Department of Software Languages Lab 2 Chapter 1

NEST: Formal Semantics and Implementation

This chapter presents NEST: a formal calculus for Sparrow. NEST serves as a precise definition of the semantics of core Sparrow abstractions. We start, in Section 1.1, by describing its syntax and . Later, in Section 1.1.4, we list the differences between the formal seman- tics of NEST and the current implementation of Sparrow. Finally, in Section 1.2, we present an implementation of the small-step operational semantics of NEST in PLT Redex.

1.1 Operational Semantics

This section starts by defining the syntax of NEST (Section 1.1.1) and its main semantic entities (Section 1.1.2). Next, it introduces a set of re- duction rules which represent the core features supported by NEST (Sec- tion 1.1.3).

1.1.1 Syntax

Figure 1.2 lists the abstract syntax of NEST, which is composed of three main syntax subsets. The first one, Ps, describes the definition of all dif- ferent types of patterns supported by Sparrow. Notice that pn represent the name of the pattern, and p is used as a holder for the non-terminal expression of Figure 1.1. The second subset, Rs, describes the definition of reaction functions. A reaction function has a name ra and a

3 CHAPTER 1. NEST: FORMAL SEMANTICS AND IMPLEMENTATION

:= [(and| or ) ]* [] [, [options:

Figure 1.1: EBNF-styled grammar for the Sparrow’s pattern language.

Ps ⊆ Pattern ::= pattern pn as p

Rs ⊆ Reaction ::= reaction rn do

As ⊆ Actor ::= actor an {react_to pn with: rn}

e ∈ E ⊆ Expr ::= x | l | i | nil | λx.e | {mid, e} | let x = e in e | spawn an | send e, e | react_to pn, with: rn | remove rn, from: pn | remove_reactions pn

x, l, i ∈ VariableName, pn ∈ PatternName, rn ∈ ReactionName, an ∈ ActorName

Figure 1.2: Abstract syntax of NEST.

4 1.1. OPERATIONAL SEMANTICS body e. The body of a reaction has access to three implicit arguments: a list of matched messages, a list of intermediate results, and the cur- rent state of the actor. Reactions like patterns are second-class entities. The lockup of both abstractions, as well as their binding (association), is globally scoped within an actor. The third subset, As, describes how to define new actors. The actor construct receives two arguments: the actor name an, and a set of bindings between patterns and reactions previously defined. Finally, the above subsets are glued together by expressions e supported by their base language together with a set of built-in functions. The built-in function react_to with: binds a pattern with name pn to a reaction with name rn. Reactions are executed in the same order they were added to a pattern. The remove from: function unbinds a reaction with name rn from a pattern with name pn. In contrast, remove_reactions remove all reaction associated to a pattern with name pn. After the def- inition of an actor, it can be initialized using the spawn primitive. This function takes a single argument, the actor name an, and return the id (aid) of the new spawned actor. Finally, lambdas are denoted by λx.e, and they correspond to standard Elixir anonymous functions. In addition, lo- cal variables can be introduce via let x = e in e.

K ∈ Configuration ::= A Configurations a ∈ A ⊆ Actor ::= Ahaid, Q, (pn, rn), ei Actors M,L ∈ MessageList ::= {mid, v} List of Messages v ∈ Value ::= nil | aid Values I ⊆ IntResult ::= v | (x, v) Intermediate Results e ∈ E ⊆ Expr ::= ... | aid Runtime Expressions

aid ∈ ActorId, mid ∈ MessageId, x ∈ VariableName,

Figure 1.3: Semantic entities of NEST.

1.1.2 Semantic Entities

Figure 1.3 shows the semantic entities of NEST. We use different font styles to distinguish our semantic entities syntactically. For example, the calligraphic letter A represent a constructor. Regular uppercase letters like K and M denote sets or sequences. Parentheses represent a pair,

5 CHAPTER 1. NEST: FORMAL SEMANTICS AND IMPLEMENTATION while curly brackets denote a tuple like in Erlang and Elixir. Actors and messages have different types of identifiers (or ids for short), denoted aid and mid respectively. The state of a NEST program is represented as a configuration K, which is composed of a set running actors. Each actor consists of an identifier aid, a queue Q of messages to be processed, a set of pattern- reaction bindings (pn, rn), and an expression e that is currently evaluated. NEST inherits all primitive values supported by Elixir. To keep simple our semantics those values are not included in our definition, instead only those used by our reduction rules are included (e.g., nil, aid). Messages are represented as Elixir tuples where the first element aid denotes the type of a message, and a list of values v follows it. Intermediate results of pattern transformations I are represented as a set of values v or pairs x, v. The first element of a pair corresponds to a variable’s name and the second to its value. These variables can be used in guard expressions, and they are also accessible in the body of a reaction. Finally, the subset of expressions e is extended to include actor identifiers aid since a sub- expression may reduce to an actor identifier before being reduced further. This new subset of expressions is called runtime expressions, and they are the base operation of our reduction rules

1.1.3 Reduction Rules 1.1.3.1 Evaluation Contexts

NEST uses evaluation contexts [FFF09] to determine which subexpres- sions of an expression should be fully reduced before the rules can further reduce the compound expression itself.

e ::=  | let x = e in e | {mid, v, e, e} | send(e, e) | send(e, e)

e denotes an expression with a hole. Each occurrence of e announces to our computation rules about an expression with a possible hole. A hole has the purpose of identifying the next subexpression to reduce in a compound expression. We use the notation e[e] indicates that the expression e is part of a compound expression e. As a consequence, our computation rules should reduce such expression e before they reduce further the compound expression e.

6 1.1. OPERATIONAL SEMANTICS

1.1.3.2 Notation

We use the following notation conventions to facilitate the understanding of NEST evaluations rules. The notation O = O0 · O00 concatenate two sequences or lists. Sometimes, we also use O = O0 · o as a shortcut to concatenate two lists, in this particular case o is considered as a list with a single element. At the same time, we use a similar notation to decon- structs a sequence being part of the arguments of an auxiliary function. For example, we use the notation o · O and O · o to remove the first or last element respectively from the sequence O. We represent queues as sequences of messages that are processed right-to-left, meaning that the last message in the sequence is the first to be processed. We use the ∅ symbol to denote both empty sets and empty sequences.

1.1.3.3 Evaluation Rules

NEST semantics is defined in terms of a relation on configurations, K → K0. Its evaluations rules are split into two parts to make explicit which actions can be executed in isolation by a single actor (local-rules), and which ones require interaction between different actors of a configuration (global-rules). The above rules can be applied non-deterministically, which gives rise to concurrency. NEST does not yet consider actors distributed across different devices; it relays on the default behaviour of the base actor language to handle disconnections. In other words, NEST considers all actors to remain permanently connected. A NEST program is an expression e that is reduced in an initial con- 0 figuration containing a single “root” actor Kinit = {Ahaid, ∅, ∅, nili}. The actor’s queue and the pattern-reaction binding list are initially empty. Fig- ure 1.4 outlines the set of substitution rules employed by NEST evaluation rules. Actor-local reduction rules are always initiated after taking the next message from their actor’s message queue. Such a message is then trans- formed into an appropriate expression to evaluate. Finally, that expression is reduced to a value. This process of reducing such a single expression to a value is called a turn Once a turn is completed, the next message is pro- cessed. As actor are single-threaded computation units, it is not possible to suspend a turn and start processing another message in the middle of a reduction. The execution of local rules stops when the message queue of an actor is empty, and it restarts when a new message arrives. If during

7 CHAPTER 1. NEST: FORMAL SEMANTICS AND IMPLEMENTATION

[v/x]x0 = x0 [v/x]let x0 = e in e = let x0[v/x]e in [v/x]e [v/x]x = v [v/x]let x = e in e = let x = [v/x]e in e [v/x]nil = nil [v/x]aid = aid

[v/x]send e, e = send [v/x]e, [v/x]e [v/x]spawn pn = spawn pn [v/x]react_to pn, rn = react_to pn, rn [v/x]remove rn, from:pn = remove rn, from:pn [v/x]remove_reactions pn = remove_reactions pn

Figure 1.4: Substitution rules: x denotes a variable name or the pseudo-variable, v denotes a value.

(let)

Ahaid,M, (pn, rn), e[let x = v in e]i ∪· A → Ahaid,M, (pn, rn), e[[v/x]e]i ∪ A

(match-message) 0 M , L, I, e = match((pn, rn),M)

Ahaid,M, (pn, rn), vi ∪· A 0 → Ahaid,M , (pn, rn), [L/l][I/i]ei ∪ A

(react-to)

Ahaid,M, (pn, rn), e[react_to(pn, rn)]i ∪· A → Ahaid,M, (pn, rn) · (pn, rn), e[nil]i ∪ A

(remove-reaction-from) 0 (pn, rn) = remove(rn, pn, (pn, rn))

Ahaid,M, (pn, rn), e[remove(rn, pn)]i ∪· A 0 → Ahaid,M, (pn, rn) , e[nil]i ∪ A

(remove-reactions) 0 (pn, rn) = remove_reactions(pn, (pn, rn))

Ahaid,M, (pn, rn), e[remove_reactions(pn)]i ∪· A 0 → Ahaid,M, (pn, rn) , e[nil]i ∪ A

Figure 1.5: Actor-local reduction rules. the execution of a local rule, it can not reduce an expression further, the actor is stuck. This unexpected behaviour signifies a semantic error in the program. Figure 1.5 summarizes the actor-local reduction rules.

8 1.1. OPERATIONAL SEMANTICS

• let: a “let” - expression simply substitutes the value of x for v in e according to the substitution rules outlined in Figure 1.4.

• match-message: this rule describes the processing of incoming asynchronous messages by an actor. A new message can be pro- cessed only if two conditions are satisfied: the actor’s queue Q is not empty, and its current expression cannot be reduced any further (the expression is a value v). The auxiliary function match (see Figure 1.7) tries to match the arrived message against the actor’s patterns.

• react-to: this rule associates a pattern pn with a reaction rn, where pn and rn represent the names of both abstractions. This association or binding is only possible if both pattern and reaction are previously defined within the scope of the actor. The new as- sociation is added at the end of the current set of pattern-reaction bindings.

• remove-reaction-from: this rule removes the association of a reaction rn from a pattern pn, where pn and rn represent the names of both abstractions. The auxiliary function remove (see Figure 1.8) removes the particular entry from the pattern-reaction set associated with the specified reaction and pattern.

• remove-reactions: this rule removes all associations of reactions from a pattern pn, where pn the name of the pattern. The auxil- iary function remove_reactions (see Figure 1.8) removes the entries from the pattern-reaction set associated with the specified pattern.

Actor-global reduction rules support operations that require interaction between different actors of a configuration. Figure 1.6 summarizes the two actor-global reduction rules supported by NEST.

• spawn-actor: this rule describes the reduction of spawning a new actor. When an actor aid reduces an actor literal expression, a new 0 actor aid is added to the configuration. The queue of the new actor is populated with a {:init} message that will use as a starting point to initialize the actor’s patterns network. We can view such network as a pattern engine that will be responsible for matching each incoming message against its patterns. Upon a full match,

9 CHAPTER 1. NEST: FORMAL SEMANTICS AND IMPLEMENTATION

(spawn-actor) 0 aid fresh 0 actor an{(pn, rn) } ∈ Ac

Ahaid,M, (pn, rn), e[spawn(an)]i ∪· A 0 0 0 → Ahaid,M, (pn, rn), e[aid]i ∪ Ahaid, {:init}, (pn, rn) , nili ∪ A

(send-message) 0 Ahaid,M, (pn, rn), e[send(aid, {mid, v})]i ∪· 0 0 0 0 Ahaid,M , (pn, rn) , e i ∪· A 0 0 0 0 → Ahaid,M, (pn, rn), e[nil]i ∪ Ahaid, {mid, v}.M , (pn, rn) , e i ∪ A

Figure 1.6: Actor-global reduction rules.

this pattern engine will execute the reactions associated with the matched pattern. The spawn function reduces to an identifier to the new actor, allowing the creator actor to communicate further with the new actor.

• send-message: this rule describes the reduction of an asynchronous message sent to an actor. After a message is sent, a new message is 0 appended to the queue of the recipient actor aid. NEST only sup- ports one-way asynchronous messages, and it assumes that messages are always delivered to their recipients. The message send expression evaluates to nil.

1.1.4 NEST compared to Sparrow

In this section, we list the differences between our formal semantics and the actual implementation of Sparrow.

• NEST is built around a base language, which is not included in our formalization. Sparrow is built on top of Elixir as a domain-specific language. The base language of NEST is limited to a subset of Elixir: only primitive values and comparison/logic operators are used in our formalization.

• In NEST the debounce and interval operator implemented in Sparrow are not supported. Only the window operator can be used to declare unquantified accumulation patterns.

10 1.1. OPERATIONAL SEMANTICS

0 match((pn, rn) · (pn, rn),M) = M , L, I, e where pattern pn as p ∈ PL reaction rn do e ∈ RL M 0, L, I, (lv, v) = match_pattern(p, M)

match((pn, rn) · (pn, rn),M) = match((pn, rn),M)

match_pattern({mid, at},M ·{mid, v}) = M, {mid, v}, ∅, (lv, v) where (lv, v) = match_attr(at, v)

00 0 0 0 0 match_pattern(p1 and p2,M) = M ,L · L ,I · I , unify((lv, v), (lv , v )) where 0 M , L, I, (lv, v) = match_pattern(p1,M) 00 0 0 0 0 0 M ,L ,I , (lv , v ) = match_pattern(p2,M )

match_pattern(p1 or p2,M) = match_pattern(p1,M) match_pattern(p1 or p2,M) = match_pattern(p2,M)

match_pattern(p when e, M) = M 0, L, I, (lv, v) where M 0, L, I, (lv, v) = match_pattern(p, M) [v/lv]e 7−→ true

match_pattern(p [count: 0],M) = M 0, ∅, ∅, ∅ match_pattern(p [count: n],M) = M 00,L · L0,I · I0, unify((lv, v), (lv, v0)) where M 0, L, I, (lv, v) = match_pattern(p, M) M 00,L0,I0, (lv, v0) = match_pattern(p, [count: n − 1],M 0)

match_pattern(p [every: 1],M) = match_pattern(p, M) match_pattern(p [every: n],M) = match_pattern(p, [every: n − 1],M 0) where M 0, L, I, (lv, v) = match_pattern(p, M) match_pattern(p [window: {n, ut}],M) = M 00 · M 000, L, I, (lv, v) where M 0,M 00 = within_outside_window({n, ut},M) M 000, L, I, (lv, v) = match_pattern(p, M 0)

0 match_pattern(pn,M) = match_pattern(p ,M) where pattern pn as p ∈ PL lv = logical_vars(p) p0 = [lv0/lv]p lv0 fresh

Figure 1.7: Auxiliary functions used in the reduction rules.

• NEST patterns can not combine quantified (e.g., count) and un- quantified (e.g., window) operator at the same time. However, Spar-

11 CHAPTER 1. NEST: FORMAL SEMANTICS AND IMPLEMENTATION

match_pattern(p fold(n, λ),M) = M 0, L, I · v, (lv, v) where M 0, L, I, (lv, v) = match_pattern(p, M) v = foldl(n, λ, L)

match_pattern(p bind(x),M) = M 0, L, I · (x, v), (lv, v) where M 0, L, I · v, (lv, v) = match_pattern(p, M)

match_attr(lv · at, v · v) = (lv, v) · match_attr(at, v) match_attr(v · at, v · v) = match_attr(at, v) match_attr(∅, ∅) = ∅

unify(∅, (lv, v)) = (lv, v) unify((lv, v) · (lv, v), (lv0, v0)) = unify_single((lv, v), (lv0, v0)) · unify((lv, v), (lv0, v0))

unify_single((lv, v), ∅) = (lv, v) unify_single((lv, v), (lv, v) · (lv0, v0)) = ∅ unify_single((lv, v), (lv0, v0) · (lv00, v00)) = unify_single((lv, v), (lv00, v00)) if lv =6 lv0

remove(rn, pn, (pn, rn) · (pn, rn)) = (pn, rn) 0 0 0 0 remove(rn, pn, (pn, rn) · (pn, rn)) = (pn, rn) · remove(rn, pn, (pn, rn))

remove_reactions(pn, (pn, rn) · (pn, rn)) = remove_reactions(pn, (pn, rn)) 0 0 0 0 remove_reactions(pn, (pn, rn) · (pn, rn)) = (pn, rn) · remove_reactions(pn, (pn, rn)) remove_reactions(pn, ∅) = ∅

Figure 1.8: Auxiliary functions used in the reduction rules (Cont.).

rows supports the particular combination of count and window op- erators in its patterns.

• NEST doesn’t support negated patterns. Furthermore, its cannot detect a strict sequence of messages; the sequencing operator is not supported.

• NEST supports two of the three filter mechanism for messages im- plemented in Sparrow: pattern-matching and regular guards. In con- trast, Sparrows supports inline guards which are a syntactic sugar to write more compact filter expressions.

• NEST doesn’t support advance attribute operators (e.g., alias, may be equal, and must be equal) useful for the definition of collection patterns. However, Sparrow supports all the attribute operators mentioned above.

12 1.2. NEST CALCULUS IN PLT-REDEX

1.2 NEST Calculus in PLT-Redex

Source Code available online1.

1.2.1 PLT Redex in a Nutshell

PLT Redex (or Redex for short) [FFF09] is a domain-specific language in Racket for formalizing operational semantics with powerful pattern match- ing capabilities. Furthermore, Redex provides a stepper for small-step op- erational semantics, hand-written/randomized unit test suit, inspectors for reduction graphs, and other tools integrated into the DrRacket IDE. By allowing language designers to write down the language grammar, re- duction rules, and relevant meta-functions for their calculi, Redex can help them to validate their implementations against their specifications at a low cost.

1.2.2 Basic Example of the PLT-Redex Semantics 1.3 Conclusion

1NEST PLT Redex - https://github.com/rhumbertgz/nest-plt-redex

13 CHAPTER 1. NEST: FORMAL SEMANTICS AND IMPLEMENTATION

14 Bibliography

[FFF09] Matthias Felleisen, Robert Bruce Findler, and Matthew Flatt. Semantics Engineering with PLT Redex. The MIT Press, 1st edition, 2009.

15