Properties... the next frontier. Well, at least if you can't stop yourself from watching the Java blog space, which is abuzz with discussions about this topic. Are Properties the next feature to save the world, give us that so desperately needed silver bullet, and finally make Java developers feel good in their skins again? Bah... it's boring to just theorise about the superpowers of Properties. How about we see if Properties can be useful by actually adding them to Ruby and see how they fare... and don't worry: no Lexers, Grammars, or Language Specs were harmed during the production of this article.
Embedded DSL
How could we add Properties to Ruby? Well, let's try to implement an Embedded DSL. The best way to start a DSL is to doodle around and sketch something that looks right.
Let's see... C# properties in Ruby would look something like that:
class CruiseShip
property direction
property speed
end
With the basic look done, we see that this is not quite legal Ruby code, but it's not far off. If you load this class, Ruby will complain about "direction" and "speed" being unknown.
Let's give the property call a property name in a way that doesn't get evaluated on load. Yes: Symbols to the rescue!
class CruiseShip
property :direction
property :speed
end
The code parses and loads... but it still throws an error: NoMethodError: undefined method 'property' for CruiseShip:Class
To solve this we use a simple fact: Ruby class definitions are more than just declarations, they are actually executed at load time. When this line is loaded:
property :direction
a function with name "property" is searched and then called with parameter :direction. How do we get the property method in there? Well, for now, let's just do this:
def property(sym)
# do some stuff
end
class CruiseShip
property :direction
property :speed
end
And there we are: the code loads without complaints. The property definition on top of the file isn't pretty, but we'll deal with that later and make it reusable.
Let's flesh out the code in the property function. property is called when the class definition is executed, which means it can add methods to the class. With this, we can define a method, which will then be part of the class. Add this code to the property function:
define_method(sym) do
instance_variable_get("@#{sym}")
end
This causes the equivalent of this code to be added to the class:
def direction
@direction
end
This is the getter for the direction property, the code for the setter can be added in the same way:
define_method("#{sym}=") do |value|
instance_variable_set("@#{sym}", value)
end
This isn't particularly useful yet... actually, the same thing could have been done with attr_accessor :property which is already available in Ruby.
What? All this text and then the feature we implemented is already available? Not, quite. With Properties we want more than just adding setters/getters on a class. Properties should make it possible to register listeners that get notified when the value of a property changes. After all, the trusty old Observer pattern has served us well. Except, that using it in Java always involves a lot of tedious typing and boilerplate code. There's the code that actually calls all the listeners, which is always the same... but before the notification comes the registration, which consists of defining add/remove methods that allow to register/unregister listeners. Since this code is specific to the property names (you want add_direction_listener,...), there is no way to have an automatic way to do this in Java.
But wait! This is a Ruby, which lends itself well to MetaProgramming, the art of having that lazy bum of a computer do the boring work for us, leaving us more time to smell the roses and feed the cat.
There is already some code that implements the setter
define_method("#{sym}=") do |value|
instance_variable_set("@#{sym}", value)
end
Well... couldn't that just send out the notifications as well?
define_method("#{sym}") do |value|
instance_variable_set("@#{sym}", value)
fire_event_for(sym)
end
The other bit missing here are the listener management methods. Here we just use the same define_method trick again, ie. in the property function, define a method for this particular event:
define_method("add_#{sym}_listener") do |x|
@listener[sym] << x
end
Methods for removing or accessing listeners would be handled the same way. The code for setting up the listener lists and the rest is left to the reader as exercise. (Stop moaning, it's just a few lines of code each)
Using the code
Let's see how all this works.
h = CruiseShip.new
h.add_direction_listener(Listener.new)
h.add_bar_listener lambda {|x| puts "Oy... someone changed the property to #{x}"}
h.bar = 10
This will output: "Oy... someone changed the property to 10"
Dandy... that was easy. But before we continue we some more fun, let's do a bit of cleaning.
Wrap it up an tie a bow around it
Now... how do we get all this inside a class? This is where a very useful feature comes in: Mixins. These are just ordinary Ruby Modules that are mixed into anclass definition. Sounds odd? Nope, it's as simple as this:
class Ship
extend Properties
end
This will make all functions from the Properties Module available from inside Ship. This is how the simple notation for the property call works. Other languages would have to resort to Inheritance for this, ie. define the method in a class and force users to extend that. With Mixins, the class hierarchy stays clean, and you can just mix in (oh... that's where the name comes from!) the features you need.
This means, that we can move the code in our demo code to its very own Mixin:
module Properties
def property(sym)
# all the nice code
end
end
This is why you can do this
class Ship
extend Properties
property :direction
property :speed
end
This has the nice effect that it also records the fact that this class is using the Properties Mixin. When reading this code, if you're not yet familiar with this enhancement yet, just look at eithern the documentation for the Properties Mixin or look at its source.
Let's have some fun
With the basic Property goodies in place, let's have some fun. The concept of Design by Contract allows you to define some constraints and invariants of your class. Statically typed languages have a primitive version of this:
void foo(int x)
means that only int values can be passed in. Of course... what exactly is an int? Why are values in a range of 2^31 so interesting, particularly if we actually want the :speed property to accept only values in a range of 0..300 (this ain't your average cruise sheep...). Another way of looking at this are Gilad Bracha's idea of Pluggable Type Systems, ie. instead of relying on often inadequate existing type systems, simply define your own and allow to define type ranges in a more declarative way, instead of having to clutter the code with lots of if/else constructs and defensive programming.
So, why the lecture on DbC and Pluggable Type Systems? Well, since we're busy extending the language, let's add something useful, like a feature for specifying constraints on the property values.
This is where you can get creative and decide how you want this to look. This can take a range, or any other named type. You could also throw in some predicate
property(:speed) {|v| (v >= 0) && (v < 300) }
The implementation for this simple:
def property(x, &predicate)
define_method("#{sym}=") do |arg|
if(predicate)
if !predicate.call(arg)
return
end
end
instance_variable_set("@#{sym}", arg)
fire_event_for(sym)
end
end
This does leave the nice side effect that the value of the property will always be in a defined range inside the class, without having to clutter the code with range, type or nil checks. You just define all specific constraints of the property in one central space, and be done with it.
Actually, this speed constraint could be defined in a much more concise way. This is outside the scope of this article, but as an exercise, try to implement something like this:
property :speed, in(0..300)
Note that this is not legal Ruby code (in
is a Ruby keyword). It's useful to start from the way we want the code to look and then proceed to make it Ruby code.
Enjoy.
Conclusion
Of course, the latter part was not something that would get added to a Properties feature, simply because this kind of typing should be unrelated to the Property and Notification features. But in Ruby, you can just mold the language around your needs... this is just an example of what's possible.
Arguments against Embedded DSLs are manifold. Like the idea that they make code harder to read. And it's true. Just like this is difficult to read: print(x). How in the world could you know what this function call does? Well... you could read the documentation or peek at it's source code. But how is this different from the DSL implementation? It isn't.
The techniques used here are very simple, and every developer who doesn't get scared of words such as Polymorphism or Recursive Type Realization (or whatever 100 U$ words are popular now) will understand them in a moment, and they make the code so much more concise, to the point and maintainable.