ActionScript, RIA and DSL part 1 ½
Introduction
This post is actually a somewhat intermezzo, and wasn’t in my plans. But in my writings of part 1, I happed to post a non working example of a DSL.
I didn’t see the fault my self, but Dmitry Miterev was so kind to point me attention to it. - so I guess this blog post is dedicated to him
Background
When I started looking into the opportunities for writing a DSL with AS3, I did a lot of testing and tweaking to see how the language features could help enable me to write a more fluent DSL. Especially the getter / setter features was of great interest as these in their nature removed a lot of noise from the language.
The bad example
In my first post, I pointed out, that to get the Coffee example to work, you could just use a setter returning a reference to the object, containing the property. This can be done, but to things are wrong in the example:
1) It is not possible to type the return type if you want to return the instance:
package { public class MyClass { //Not legal setter public function set myProperty(value:String):MyClass { return this; } //Legal setter public function set myOtherProperty(value:String):* { return this; } } } |
So as you can see, you can mark the setter as ” * ” for an arbitrary value.
2)It is not the feature we are looking fore to solve the design we want in the Coffee example; we need the getter / setter to make the Coffee example work, but the above feature, which for sure can come in handy in DSL cases, is not the one used to create the example.
So what went wrong
I guess writing this post, and playing around with DSL’s in AS 3 for a longer period of time, made me grab, at least what I thought was the, “last working Coffee example” and use that. I guess the lesson is learned; compile and test code before blogging it.
The right example
So without further ado I’ll jump into the Coffee example I actually got to work, when playing around. One thing different though is that the working implementation do not allow for “direct value” like the number defining the temperature of the coffee.
Having reviewed the working example again, I think the best description of the “pattern” if a such can be derived, would be “Reverse Chain Method”, meaning that the last object in the chain, is created first and collects the values from the previous objects in the chain.
But before I turn this blog into a drag like the first part, lets look at the code. - it will also put my definition into context.
The structure
The idea of this way of getting the look and feel of the DSL is to replace the actual class getting instantiated by a subclass (or subclass of subclass) which has the same properties as the base class. So to identify the players in our exampel:
The Coffee class, the class (we think) we manipulate
The Coffee Value, the class representing a value we want to assign to the coffee, say a temperature, a size etc. The CoffeeValue class is a helper class with the logic to perform the revers chaining.
The Concrete value classes, these classes are the concrete implementations of values, so the actual Temperature, Size or Consumption class. These are all sub classes to CoffeeValue, which it self is a subclass to Coffee. To mark the and use the polymorphisms we have a set of marker interfaces, one for CoffeeValue, Template, Size etc.
The complete diagram looks like this:
So to start looking at the language we want to build, we have the Coffee example below. As you can see, in this example the temperature now refers to a static property, instead of a value.
function makeCoffee() { var latte:Coffee = new Coffee() .water = ConcreteWater.NO_WATER .type = ConcreteCoffeeType.TAZO_CHAI_TEA_LATTE .fat = ConcreteFatContent.NON_FAT .size = ConcreteCoffeeSize.GRANDE .where = ConcreteConsumption.FOR_HERE .temperature = ConcreteTemperature.HOT } |
Coffe Time
So what happens inside the various classes. I’ll start be looking at the Coffee class, to see how the internals in structured.
package { public class Coffee { protected var _size:CoffeeSize; protected var _where:Consumption; protected var _fat:FatContent; protected var _water:Water; protected var _temperature:Temperature; protected var _type:CoffeeType; public function Coffee() { } public function set size(value:*):void { _size = value; } public function get size():CoffeeSize { return _size; } public function set where(value:*):void { _where = value; } public function get where():Consumption { return _where; } public function set fat(value:*):void { _fat = value; } public function get fat():FatContent { return _fat; } public function set water(value:*):void { _water = value; } public function get water():Water { return _water; } public function set temperature(value:*):void { _temperature = value; } public function get temperature():Temperature { return _temperature; } public function set type(value:*):void { _type = value; } public function get type():CoffeeType { return _type; } } } |
What is worth noticing in Coffee is that our setters expect an arbitrary data type; this is needed for us to make the chain move backwards, as the same object is being send in to each setter trough out the chain.
Besides the setters the properties is made protected as we need our CoffeeValue helper class to have access to these variables when the chain moves.
Chain, Chain, Chaaiiiin..
Below is the flow of the chain.

So to recap; The chain starts from the last part of the language, and what happens is, that each line in the Coffee order, which is actually a new CoffeeValue being instantiated, gets collected by the last object in the Chain, finally ending in the “latte” variable.
So lets see what is inside the CofeeValue class:
package { public class CoffeeValue extends Coffee implements CoffeeSize, CoffeeType, Consumption, FatContent , Water, Temperature, ICoffeeValue { protected var _value:*; private var _property:String; public function CoffeeValue(property:String,value:*) { super(); _property = property; _value = value; this["_"+_property] = this; } override public function set size(value:*):void { createRef(value); } override public function set type(value:*):void { createRef(value); } override public function set temperature(value:*):void { createRef(value); } override public function set fat(value:*):void { createRef(value); } override public function set water(value:*):void { createRef(value); } override public function set where(value:*):void { createRef(value); } public function get value():* { return _value; } private function createRef(assignee:*):void { assignee["_"+_property] = this; } } } |
The trick
So in the CoffeeValue class we do the actual trick. As the class subclasses the Coffee, we override all the setting parts of our setter / getter properties in the Coffee. When we instantiate a CoffeeValue object, we pass two parameters:
1) is the textual name of the property our instance of the CoffeeValue is representing; in an example, if I wanted my object to be the value object for the water, I would pass “water” as the first parameter.
2)The next value is the actual value of the property, in our example I have kept it simple (or as simple as possible
) and just used strings, so in this context it would be “no water” as the value.
Both parameters is saved for later.
What??
So I mentioned the overriding of the setters. What each setter is doing, is calling a private method called “createRef”. What happens in the chain is that the various setters is being called by the various concrete classes. Thus, we need what ever calling class, which has assigned it self as value to the setter, to get it’s property set to the value the called class represents. Puuhhh, that was a tough sentence, but lets put some variables to it. Class’s type = A_TYPE’s size = B_SIZE’s temperature = C_TEMPERATURE. So in this expression, the A,B and C represents the Concrete implementations, which is just different pairs of parameters send to the CoffeeValue. So the chain starts from behind, and C is passed in to B’s set temperature, after that C is passed on to A’s type setter.
So what we make sure in the CoffeeValue setters, -> createRef, is that the class handling the setter, which knows which property it represents (remember it got that from the constructor) assigns it self to the object collecting stuff trough the chain (the last object in the expression or C in our above simplification).
The value parameter is simply saved and used as a easy way to retrieve the value of the CoffeeValue by a get value method.
Concrete Concrete
Don’t worry, it takes a little to get your head around. - and we have yet not seen the actual implementation of a concrete class.
In the DSL I have created the language constructs as “static constants” or that is what they look like at least.
But lets dissect the ConcreteCoffeeSize class (you would of cause go about calling the classes some else than prefixing them with “concrete” but to illustrate what is what I have decided to do so).
package { public class ConcreteCoffeeSize extends CoffeeValue { public static function get GRANDE():CoffeeValue { return new ConcreteCoffeeSize("grande"); } public static function get VENTI():CoffeeValue { return new ConcreteCoffeeSize("venti"); } public function ConcreteCoffeeSize(value:String) { super("size",value); } } } |
So worth noticing is that this simple class, which merely would normally be a class, has two static getters, each of them returning a new instance of ConcreteCoffeeSize, each with a different setting in value parameter. The constructor of the ConcreteCoffeeSize, is typed to handle the value, given, and providing the property which the class it self represents, to the super() constructor. You could of cause have tons of other logical things inside each concrete class, which differentiated the classes more than just a simple textual value. But for the simple example, and the fact that I’m not building a DSL for employees at StarBucks, I’m an settling for less this time
Outro
I guess this wraps this bastard of a blog post up.
Just to touch briefly on the Interfaces used, they are empty except the ICoffeeValue which defines the “get value” function implemented by CoffeeValue.
There is a lot of great possibilities in using a reversed method chain, and definitely room for improvements in my above approach.
Feel free to ask or comment, if there is anything you want to dive into. - maybe you have an even easier way to accomplish the same structure.
Happy Coding
Cheers