WORK-IN-PROGRESS: - this material is still under development
Arrange blocks of code in a data structure to implement an alternative computational model.
Programming languages are designed with a particular computational model in mind. For mainstream languages, this model is an imperative model with code organized in an object-oriented way. This approach is currently favored because it's worked out to be a suitable compromise between power and understandability. However this model isn't always the best one for a particular problem. Indeed often the desire to use a DSL comes with a desire to use a different computational model.
Active Model allows you to implement alternative computational models within an imperative language. You do this by defining a model where the links between elements represent the behavioral relationships of the compuational model. This model usually needs references to sections of imperative code. You then run the model either by executing code over it (procedural style) or by executing code within the model itself (object-oriented style).
As we write software, we regularly build models of the bits of the world the software is working with. An catalog system captures information about products and prices, a media web site has news stories, advertising, and tags that help to describe how they should go together. These models may be pure data structure (data models) or compose data with the code that manipulates them (object models). But even in an object model the flow of processing is dicatated by the code. The data it operates on is different, and its differences cause changes in detail of the processing, but the broad flow remains the same.
The state model of secret panels that I opened this book with, is a different kind of beast. Depending on which state model I load a particular system with, I get a big change in the overall behavior of the system. Essentially the instantiation of the state model is the program. Certainly there is the general Semantic Model of a state machine, and this is a constant factor and a constraint on what any particular state machine can do. But in a very real sense the program that executes is the configuration of a particular state machine.
When a model takes the primary behavioral role in a system I call this an Active Model. As with most boundaries in software, the one between active and passive models is fuzzy, but I find the classification useful. Once an Active Model is present the boundaries between what is data and what is program become very fluid and we enter a world with new possibilities and new problems. Some software communities relish this world - the lisp community is particularly strong on the duality of code and data - but for many developers it's a world that's both entrancing and scary.
Active Models exist independently from DSLs in that you can have an Active Model in a system without a DSL in sight and get most of the benefits of using one. The DSL's role here is to make it easier to program the Active Model by providing a language in which you can desribe your intentions more clearly. The examples I started with with the difference between the command-query API and the various DSLs illustrate this point. One of the hardest parts in using an Active Model is to figure out what it's supposed to do - a DSL can be a big help in overcoming that.
When I created the initial state machine example, I deliberately made it so that all the behavioral elements could be described through simple data. The actions in teh state machine are simply represented by transmitting a command code. It's common, however, for Active Models to interplay much more closely with imperative code. In another state machine I might want actions to do a wider range of things, or put conditions on my transitions as guards. To do this within the Active Model would mean complicating the Active Model with a range of imperative expressions that I already have in my host programming language. Often a better alternative is to embed regular programming language code into the Active Model data structure.
A good example of this is a rule in a Production Rule System. Such a rule has two parts: a boolean condition and an action. It's often useful to represent these in the host language.
The most natural way to do this is with a closure.
rule.Condition = j => j.Start == "BOS";
rule.Action = j => j.Passenger.PostBonusMiles(2000);
Closures work well becuase they allow you to easily embed arbitrary blocks of code into data structures. They are the most direct statement of my intention here. The big drawback of using closures for this is that many languages don't have them. When this happpens you need to resort to some work-arounds.
Probably the easiest work-around is to use a Command. To do this I create little objects that wrap a single method. My rule class then uses one for the condition and one for the action.
class RuleWithCommand {
public RuleCondition Condition { get; set; }
public RuleAction Action { get; set; }
public void Run(Journey j) {
if (Condition.IsSatisfiedBy(j)) Action.Run(j);
}
}
interface RuleCondition {
bool IsSatisfiedBy(Journey j);
}
interface RuleAction {
void Run(Journey j);
}
I can then set up a particular rule by making a subclass.
var rule = new RuleWithCommand();
rule.Condition = new BostonStart();
rule.Action = new PostTwoThousandBonusMiles();
class BostonStart : RuleCondition {
public bool IsSatisfiedBy(Journey j) {
return j.Start == "BOS";
}
}
class PostTwoThousandBonusMiles : RuleAction {
public void Run(Journey j) {
j.Passenger.PostBonusMiles(2000);
}
}
Most of the time I can reduce the amount of subclasses I need by parameterizing the commands.
var rule = new RuleWithCommand();
rule.Condition = new JourneyStartCondition("BOS");
rule.Action = new PostBonusMiles(2000);
class JourneyStartCondition : RuleCondition {
readonly string start;
public JourneyStartCondition(string start) {
this.start = start;
}
public bool IsSatisfiedBy(Journey j) {
return j.Start == this.start;
}
}
class PostBonusMiles : RuleAction {
readonly int amount;
public PostBonusMiles(int amount) {
this.amount = amount;
}
public void Run(Journey j) {
j.Passenger.PostBonusMiles(amount);
}
}
In a language without closure support, something like this is usually where I would prefer to go.
Another option is to use the name of a method and invoke it using reflection. I don't like this approach as it circumvents the mechanisms of the underlying environment just a bit too much.
I've described using commands as a work-around, and when you're looking at it from purely the Active Model sense that's true. However if you're populating the Active Model with a DSL, then commands become more attractive. In many situations the DSL will wrap common cases in paramaters anyway, which leads naturally to paramaterized commands. To use the full expressiveness of closures in the DSL means either using closures in an internal DSL or Foreign Code in an external DSL. The latter, in particular, is something you should try use only rarely.
A DSL is a valuable tool for an Active Model since it allows people to configure the instance of a model using a programming language making its behavior more explicit. However a DSL is not really enough to work with an Active Model as it gets more complicated. Other tools come in handy.
It's often difficult to follow what an Active Model is doing since it uses a computational model that people are less familiar with. As a result it's particularly important to use some kind of tracing when executuign the model. The trace should capture how the model processes its inputs, leaving a clear log behind of why it did what it did. This greatly helps answering the question - "why did the program do that?"
A model can also produce alternative visualizations of itself, where you take the model and get it to produce a descriptive output of a model instance. A graphical description is often very useful. I've seen some very handy visualizations produced using graphviz - which is tool for automatically laying out node and arc graph structures. The state diagram picture of the secret panel control system is a good example of this. Various kinds of reports that show what the model looks like from a different persepective to the input DSL can also be useful.
Visualizations like this are like a simple equivalent of the multiple projections of a language workbench. Unlike such projections, they aren't editable - or rather the cost to make them editable is usually prohibitive. But such visualizations can still be extremely useful. You can build them automatically as part of your build process and use them to check your understanding of how the model is configured.
The glib answer to when to use a Active Model is that you use it when you want to use an alternative computational model. This, of course, begs the question of when you do want to use an alternative computational model? Here it's a qualitative decision on what best seems to fit your problem. I don't have any rigorous approach to making this decision. My best suggestion is to try expressing the behavior according to a different computional model and see if that seems to make it easier to think about. Doing this often means prototyping a DSL to drive the model, since the Active Model alone often doesn't provide enough clarity.
A lot of time this involves considering a common computational model. The other patterns in this section give you a starting point, if one of these seems to fit, then it's worth a try. It's less common to find you want an entirely new computational model, but it isn't unknown. Often such a realization can grow from the way a framework changes over time. A framework can begin just storing data, but as more behavior worms its way in, you can see a Active Model beginning to form.
Active Model comes with a particularly large disadvantage: Active Models can be very hard to understand. I commonly come across cases where programmers complain bitterly about how they can't understand how an Active Model works. It's as if there's a bit of magic embedded in the program and a lot of people find this kind of magic rather scary.
The scaryness comes from the fact that an active model results in implicit behavior. No longer can you reason about what the program does by reading the code. Instead you have to look at a particular model configuration to see what how the system behaves. Many developers find this enormously frustrating to work with. It's often difficult to write a clear program that expresses your intent, instead you have to decode it from a data model that's hard to navigate. Debugging can be a nightmare. You can make it easier by producing tools to help with this, but then spend time building tools rather than working on the true purpose of the software.
Usually there's one or two people around that understand the Active Model. They are big fans of it, and can be incredibly productive by using it. Everyone else, however, steers well clear.
This phenomenon genuinely puts me in two minds. I'm the kind of person who finds Active Models very powerful. I'm comfortable with finding them and using them - and I feel that a well chosen Active Model can greatly improve productivity. But I also have to recognize that they can be an alien artifact to most developers, and sometimes you have to forego the gains of an Active Model because it's not good to have a magic section is a system that people are fearful of touching. You run into the danger that if few people who understand the Active Model should move on, nobody can maintain that part of the system.
One hope I have is that using DSLs can alleviate this problem. Without a DSL, it's very hard to program an Active Model and understand what it does. A DSL can make much of that implicit behavior explicit by capturing the configuraiton of the Active Model in a language nature. My sense is that as DSLs become more common, more people become comfortable with Active Models and thus more people will be able to realize the productivity benefits they provide.