Chapter 33. Function Sequence

A combination of function calls as a sequence of statements.

image

33.1 How It Works

Function Sequence produces a series of calls, unrelated to each other except ordered in a sequence in time; most importantly, there is no data relationship between them. As a result, any relationship between the calls needs to be done through the parsing data, so a heavy use of Function Sequence means you use a lot of Context Variables.

To use Function Sequence in a readable way, you usually want bare function calls. The most obvious way to do this is to use global function calls, if your language allows it. This, however, brings two main disadvantages: static parsing data and the fact that the functions are global.

The problem with global functions is that they are visible everywhere. If your language has some kind of namespacing construct, you can (and should) use that to reduce the scope of the function calls to the Expression Builder. A particular mechanism to handle this in Java is static import. If your language doesn’t support any global function mechanism at all (such as C# and pre-1.5 Java), then you’ll need to use explicit class methods to handle the calls. This often adds noise to the DSL.

Global visibility is an obvious disadvantage to global functions, but often the most annoying problem is that they force you to use static data. Static data is often a problem because you can never be entirely sure who is using it—particularly with multithreading. This problem is particularly pernicious with Function Sequence because you need a lot of Context Variables to make it work.

A good solution for both globally visible functions and static parsing data is Object Scoping. This allows you to host the functions in a class in the natural object-oriented way and gives you an object to put the parsing data into. As a result, I suggest using Object Scoping if you are using Function Sequence in all but the very simplest cases.

33.2 When to Use It

On the whole, Function Sequence is the least useful of the function call combinations to use for DSLs. Using Context Variables to keep track of where you are in a parse is always awkward, leading to code that’s hard to understand and easy to get wrong.

Despite this, there are times when you need to use Function Sequence. Often, a DSL involves multiple high-level statements; in this case, a list of statements often makes sense as a Function Sequence as you only need a single result list and Context Variable to keep track of things. So, Function Sequence is a reasonable option at the top level of a language, or at the top level inside a Nested Closure. However, below that top level of statements, you want to form expressions using Nested Function or Method Chaining.

Perhaps the biggest reason to use Function Sequence is that you always have to start your DSL with something, and that something has to be a Function Sequence even if there’s only one call in the sequence. This is because all the other function call techniques require some kind of context. Of course, one can argue about whether a sequence with a single element is really a sequence, but that seems the best way to fit it into the conceptual framework I’m using.

A simple Function Sequence is a list of elements, so the obvious alternative is to use a Literal List.

33.3 Simple Computer Configuration (Java)

Here is the recurring computer configuration example as a DSL with Function Sequence:

image

Although I’ve indented the code to suggest the structure of the configuration, that’s just arbitrary use of whitespace. The script is really just a sequence of function calls with no deeper relationship between them. The deeper relationship is built up entirely using Context Variables.

Function Sequence uses top-level function calls, which I have to resolve in some way. I could use static methods and global state—but I hope that would offend your design taste too much for me to get away with it. So instead I use Object Scoping. This does mean that the script has to be kept in a subclass of the computer builder, but that’s well worth it to avoid using globals.

The builder contains two types of data: the content of processors and disks that it’s building up, and Context Variables to indicate what it’s currently working on.

image

I’m using Construction Builders to capture the data for the (immutable) Semantic Model objects.

The call to computer() clears the Context Variables.

image

The calls to processor() and disk() create a value for collecting the data and set the Context Variables to track what the builder is currently working on.

image

I can then capture the data into the appropriate source.

image

Specifying the speed is a little more complicated, as the call could refer to either processor or disk speed, depending on the context.

image

When the builder is done building, it can return the Semantic Model.

image

To hook all this with the script, I need to wrap the script into a subclass of the computer builder.

image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.71.106