Home > Articles > Programming

  • Print
  • + Share This
This chapter is from the book

8.10 Pattern Macros

Writing patterns is made much easier by an abstraction feature. An abstraction feature is useful in various ways.

  1. First, it lets us abstract commonly used patterns and name them. This lets us shorten the notation for patterns and write readable patterns.

  2. Second, we need to build up libraries of patterns for each application domain, say, network protocols, control systems, distributed transaction systems, supply chains for various industries, and so on—each domain has its own common patterns.

  3. Third, when we specify hierarchical systems, we need to organize our patterns hierarchically too.

Pattern macros are a simple abstraction feature that helps with all these practical problems.

If we want to define a pattern macro called PM, we write:

pattern PM (parameter list ) {pattern}; 

PM is the name of the macro. It names the part in braces, '{' . . . '}', which must be a pattern. This is called the body of the macro.

The way we use pattern macros is to call them in patterns. So, in some pattern, we can write a call like this:

. . . PM(actual parameter list ) . . . 

During pattern matching, a point is reached at which a macro call such as PM(. . .) must be matched. At this point in the matching, the parameters of the call have certain values, either objects or placeholders that haven't been bound yet. The macro call is replaced by an instance of the macro's body. To do this, the parameters in the body are replaced by the corresponding values. The resulting instance of the macro's body replaces the macro call in the pattern. This is called macro expansion because the call is "expanded" into an instance of the body, which is usually a lot bigger.

We could do many macro expansions with a text editor except when the macro is recursive—that is, the macro contains a call to itself. So macro expansion takes place at runtime—during pattern matching. And macro expansion is lazy—expansion takes place only if a match of the macro call is needed to match the pattern containing the macro call.

Example 1: A pattern macro to shorten notation

pattern Reply(Msg X) {Ack(X.header) or Time Out }; 
[ * rel ~] (Msg M)(Send(M) Reply(M) ) 

We want to shorten the "send causes an acknowledge or time out" pattern in an example in the previous section. So we define the "acknowledge or time out" piece of the pattern to be a macro called Reply. Now we can specify the pattern more succinctly with a macro call to Reply. It is shorter and more readable. The rewritten pattern specifies that each send causes a reply, which happens to be ". . . " (an instance of the Reply body).

Example 2: Another pattern macro to shorten notation

pattern Transaction( ) {(Msg M)(Send(M) (Ack(M.header) or Time Out) ) }; 

[ * rel ~] Transaction( ); 

This second pattern macro shows that if we think of the "send causes an acknowledge or time out" pattern (the part that is being repeated) as a transaction, rather than a send and a reply, we can write the example even more succinctly.

As we said before, we have to be just a little careful about how we define macro expansion of macro calls, because pattern macros can be recursive. If we just dive in and do naive macro expansion, a recursive macro call will keep on being expanded, and we will never stop. This happens in all macro facilities that can be recursive. So, macro expansion is lazy. A macro call is expanded during matching of the pattern containing the call, "as needed" to do the match.

Example 3: A recursive pattern macro

pattern Saving( ) is Deposit àSaving( ) or Empty( ) ; 

   Saving( ) 
– – matches the same finite posets as: 
[ + rel ] Deposit 

Saving is recursive. It says, "Match a Deposit that causes either another match of the pattern Saving or the empty poset—that is, it causes no events." It matches posets consisting of one or more Deposit events, all in a causal chain.

Empty is a predefined pattern macro that matches the empty poset. Empty is useful for defining other patterns, as here, where it defines the termination case in a recursive macro.

Macros can be used to define other relational operators. Here is an example of a macro defining a new structural operator, (immediate cause). This expresses a relationship between events P and Q, whereby P is an immediate cause of Q—for example, father and son, but not grandfather and grandson. That is, P causes Q and there is no event, E, such that P causes E and E causes Q.

Example 4: Immediate cause operator

pattern P Q is (P Q) not (P Any Q); 

A new relational operator, -(P, Q), is defined using the operators and not. It matches a poset if P Q matches the poset, and there is no nonempty subposet of the matching poset that matches Any and is causally between the matches for P and Q. So, the match for P must be an immediate cause of the match for Q.

  • + Share This
  • 🔖 Save To Your Account