Showing posts with label domain specific languages. Show all posts
Showing posts with label domain specific languages. Show all posts

Wednesday, October 24, 2007

Sinatra, a Ruby web framework, and Why It Matters

As followers of this blog know, I am very interested in the Ruby language itself, not just web development. But it is undeniable that Ruby has become popular largely due to web development powered by Ruby on Rails. However amazing Rails is, however, it is not the final web framework to be developed in Ruby.

When Ari Lerner first told me of the Sinatra project, I was interested to see where he was going with it. Now that I have seen it, I totally get it, and I am more interested than ever. Let's compare it to Rails, Camping, and Merb, to better understand where Sinatra fits in, and why Ruby programmers should take a look at it.

Unlike Ruby on Rails, Sinatra is not a Model-View-Controller (MVC) based framework for creating websites. If you want helper functions that help you create forms, connect to databases, or any of the other myriad of functions that Rails provides, you will not find them. Instead, Sinatra is a very simple, yet powerful, Domain Specific Language (DSL) for defining RESTful HTTP actions, and then defining how the application is going to respond to them.

Rails requires a separate routes file to define how the web application is going to respond to requests, which hooks up to the appropriate controllers/models. Sinatra defines the simplest thing that could possibly work. When you declare a new "get" or "post" action, Sinatra will automatically add the route, and simply start responding to requests that match.

Here is a simple example of a Sinatra application:


require 'rubygems'
require 'sinatra'

get '/' do
"Hello world"
end


As you can see, a Sinatra application's code is tiny, similar to Camping in that you can easily create an entire web application deployed as a single file. Unlike Camping, Sinatra does not have such a "crazy meta magic" feel to it... why's coding style is not for the faint of heart.

Like Merb, Sinatra is implemented as a Mongrel handler. Also, like Merb it is both thread-safe and has better performance than Rails. However, where Merb tries to be a "Rails-lite" for devs who are already familiar with Rails, but want a more bare-metal approach, Sinatra unabashedly takes an entirely different direction with its very minimal DSL-syntax.

Not to say that you cannot use ActiveRecord or whatever other Ruby gems you want to create your application. But unlike Rails, Sinatra's base core is incrediby minimal, and it puts the onus on the developer to decide what to include, instead of adding a bunch of things that may not be appropriate for the type of application that Sinatra would be good for.

So what would it be good for? API implmentations, quick minimal applications, and web development that does not want or need things that are included in Rails, like ActiveRecord. Control panel mini-applications, or perhaps widgets. I am looking forward to really getting into some fun with Sinatra... check back soon here for more.

Yesterday, the Ruby Inside blog mentioned Sinatra, and some of us have been digging it for the last few days. Take a look, and you will enjoy the appeal of a simplicity and elegance in development you haven't felt from Ruby for a long time... a very long time.

More Developer Fun In Los Angeles

Last night was the always entertaining Los Angeles Application Developer Meetup. L.A. is not known for the same massive quantity of developers as the bay area, for example, but having joined forces with the LA Ruby on Rails Meetup crew some time back, the monthly event is still growing.

This month's was held at the impressive new digs of YellowPages.com, who have been busy building out a huge Ruby on Rails based local search replacement for their venerable 100K+ lines of Java code. Thanks for the great sandwiches, boys... next time, buy beer!

Anyhow, four presentations took place on various fun topics of technical interest. First, Jeff Yamada and his associate (sorry I didn't catch your name) showed AIR. I then did a short presentation on Continuous Integration and CruiseControl.rb. Olmo Maldonado talked about his impressive JavaScript library MooTools, and then Ari Lerner showed his just-relased Ruby web development framework, Sinatra.

AIR
Long, long ago, I used to work with Flash and ActionScript. Most of us more "serious" programmers were always very frustrated with the language, the programming environment, pretty much everything except for the final product. Since then Adobe has introduced technologies like Flex and AIR to try to address the shortcomings of using Flash as a "real" development tool. Yamada and friend are the authors of the upcoming book "AIR Bible". AIR nee Apollo is designed to provide a way to create native OS applications, while using the toolset that Flash/Flex developers are used to. If you are into Flash/Flex already, then this is probably great for you, but I am not overly in love with so many angle brackets mixed in my code... let's just say that "it ain't Ruby". One small thing I will suggest to the boys: please try to find some relevant application to show for a demo a of a new technology. Between this, and what all of the Microsofties were show a few months back for Silverlight, no one is going to take this new desktop app paradigm seriously!

How To Get Your Code Together and Keep It That Way
Then I did a presentation myself, called "How To Get Your Code Together and Keep It That Way". It was really an exhortation to use the best practice of Continuous Integration, while showing how easy it is to get started with the ultra-cool CI tool CruiseControl.rb. If you are a Rails dev, and you are not using CruiseControl.rb yet, you should be. If you have a development project with greater than zero developers, it is worth it to setup a CI system, even just to keep yourself in check. You can get a copy of my presentation here. But don't just believe me, it is in yesterday's O'Reilly Radar too.

As I was demoing the CC.rb project page, where they are doing CI builds of both CruiseControl.rb itself and the Ruby on Rails project, I noticed that the Rails build has had failing unit tests since Oct. 3. What is up with that? Let's get on it boys!

MooTools
Next up was Olmo Maldonado, once of the authors of MooTools. MooTools is a JavaScript framework for web development along the lines of Prototype or jQuery. I had been hearing about MooTools, but hadn't really checked it out. Naturally, since both of those other JS frameworks already exist, and with Prototype really having dominated in the acceptance wars, MooTools does have a different approach on several levels to justify its existence.

Heavily optimized for speed, according to Maldonado, Moo performs much faster than either Prototype or jQuery on the slickspeed benchmark test at performing DOM selects, which is the core convenience provided by any JS framework.

Another difference is that MooTools libs are completely orthogonal. As such, the developer only needs to incorporate the specific libs that contain the functionality that is required for that application, unlike both Prototype and jQuery which require downloading the entire framework to use any part of it.

MooTools seems to have a more serious emphasis on good software devlopment practices than the othe JS framework projects. One example of this is that MooTools was developed using JSSpec, which is a Behavior Driven Development framework for JavaScript.

Already up to version 1.2, MooTools has been adopted in some very interesting places. The three demos that Maldonado showed were the iPhone version of the Pop-Cap game Bejeweled, the Ubuntu.com website, and then most curious of all, the Microsoft Expression website! What, M$ couldn't use Silverlight? Oh yeah, everyone already has Javascript... no additional download required. Whatever happened to that famous dogfooding?

MooTools is realy designed for the developer who wants to work in pure JavaScript. Most Rails devs like to use the Ruby on Rails Prototype helpers to do nice AJAX-y things like edit in-place and auto-complete functionality. MooTools does all that, plus has a bunch of UI gadgetry too, but having no inline functions, is not really compatible with the way the Rails helpers work. Perhaps someone will create a Rails plug-in that can collect up all of the MooTools Javascript, kida like what the Unobtrusive Javascript plugin does... for those Rails people who want to take advanatge of the DSL-like syntax, instead of dropping all the way into JavaScript.

Sinatra
Speaking of DSLs, the final presentation of the evening was by Ari Lerner, showing his incredibly cool new little Ruby web framework called Sinatra. In fact, I was so impressed by Sinatra, that I decided to give it its own posting. I suggest you go check it out right now!

Friday, October 19, 2007

Is Your Database Getting Tired?

Is your database getting old and tired? Mike Stonebraker thinks so. Stonebraker is a database guru and long-time researcher, and when I say database guru I mean one of the founders of Ingres, and former CTO of Informix, not just some guy who knows how to debug a stored procedure.

Here is a juicy quote from a recent article published by MIT of which he is one of the authors:

We conclude that the current RDBMS code lines, while attempting to be a "one size fits all" solution, in fact, excel at nothing. Hence, they are 25 year old legacy code lines that should be retired in favor of a collection of "from scratch" specialized engines. The DBMS vendors (and the research community) should start with a clean sheet of paper and design systems for tomorrow's requirements, not continue to push code lines and architectures designed for the yesterday's requirements.


Strong stuff! Especially for a generation of programmers for whom the relational database is just another ubiquitous part of their standard solution architecture. So is SQL the new HTML as some have claimed? Or are we heading to a new generation of database engines that are created individually for each solution space, perhaps created on the fly and defined in a domain specific language.

Tuesday, January 23, 2007

Speaking The Same Language

The art of successful software development is matching the delivered software to the needs of the users. Achieving this requires that the developers and the users speak the same language regarding the problem to be solved. James Shore has a new chapter called Ubiquitous Language in his upcoming agile development book that summarizes this idea nicely.
Martin Fowler's seminal article on Language Workbenches also has really influenced my thinking. I guess the difference to me is that where Ubiquitous Language is about getting the developers to understand what the users are talking about, Language Workbenches are about building software systems using tools to translate the domain specific language into runnable code.
The foundations of these ideas have to do with human communication as much or more than they have to do with technology. For decades, good consultants have been taught "learn to speak the customer's language". The movement behind domain specific languages is just a formalization of something that has been going on for a long time. I'm pretty sure that Peter Drucker was doing something like this back in 1954 or so...just not using computers.

Friday, January 12, 2007

Ruby Domain Specific Languages - The Basics (Part 4)

In the previous three entries in this series of postings, I have been exploring the basics of creating domain specific languages using Ruby. Since I cannot disclose details of my big financial service client's DSL, I am using this made up example to illustrate the techniques that are useful when building your own Ruby DSL. So far we have created a "PetShop" domain that allows us to interact with the Pets. Defining the behavior of new Pets is the purpose of this DSL.

For example, let's say we now want each Pet to be able to do some trick, when called on to do so. When you are defining behaviors, it is very useful to define a recipe-like syntax in a DSL.

Here is an example:

pet "Toto" do
when_performing_tricks do
sit
speak
end
end


This is just a description or recipe of what the animal is supposed to do when asked to perform it's trick. When we really want our Pet to perform, we simply say:

pet.perform


This produces the following output:

Toto will now perform...
Toto is sitting...
Toto says 'Woof!'
Let's hear some applause for Toto!
Slugworth will now perform...
Slugworth is sitting...
Let's hear some applause for Slugworth!
Tweety will now perform...
Tweety is sitting...
Tweety says 'I thought I saw a putty tat!
Tweety is flying...
Let's hear some applause for Tweety!


The Pet will do whatever tricks it knows, using this little simple bit of Ruby magic:

def self.when_performing_tricks(&routine)
@routine = routine
end

def perform
puts "#{name} will now perform..."
@routine.call
puts "Let's hear some applause for #{name}!"
end


The "when_performing_tricks" method simply stores the block, and executes it only when the time comes to "perform".

So how does the Pet know what is entailed in each trick? I have put the "sit" trick into the base Pet class (every Pet knows how to sit):

def self.sit
puts "#{name} is sitting..."
end


We can also more importantly define custom tricks per type of Pet. A simple bit of Ruby metaprogramming goodness helps us out:

def self.define_trick(name, &trick_definition)
singleton_class.class_eval do
define_method name, &trick_definition
end
end



The "class_eval" methos lets us evaluate the inside expression in the context of the class object, not just a particular instance of the object. Another bit worth noting is the helper method that I have added to the DSLThing base class called "singleton_class" that looks like this:

def self.singleton_class
class << self; self; end
end

This is just a short cut to get to the class instance object, known better to Rubyists as the "singleton_class".

Lastly, the "define_method" method then lets us define a new method for the trick. When we want to define a new trick for a particular Pet, we can simply define it like this:

define_trick "speak" do
puts "Toto says 'Woof!'"
end


Putting it all together, here are our new definitions of the tricks our Pets can do:

pet "Toto" do
when_performing_tricks do
sit
speak
end

define_trick "speak" do
puts "Toto says 'Woof!'"
end
end

pet "Tweety" do
when_performing_tricks do
sit
speak
fly
end

define_trick "speak" do
puts "Tweety says 'I thought I saw a putty tat!"
end

define_trick "fly" do
puts "Tweety is flying..."
end
end

pet "Slugworth" do
when_performing_tricks do
sit
end
end


One other interesting technique of note is that we are actually defining a new type of Pet by dynamically declaring a new class based on the Pet class. Otherwise, our custom tricks for one type of Pet might interfer with the custom tricks of another. The solution to this is using the "Object.const_set" and "Object.const_get". Here is the code I used:

def self.pet(name, &blk)
@pets ||= Hash.new
klass = Class.new(Pet)
Object.const_set(name, klass) if not Object.const_defined?(name)
p = Object.const_get(name).new
p.name = name
p.class.class_eval(&blk) if block_given?
p.copyvars
@pets[name] = p
end


In this posting I have used the DSL recipe technique, and created dynamic methods and classes. Combining these techniques can allow for a very powerful and yet concise syntax when creating your own Ruby domain specific languages.

Here is the complete listing of the code from this post:

class DSLThing
def copyvars
self.class.instance_variables.each do |var|
instance_variable_set(var, self.class.instance_variable_get(var))
end
end

def self.singleton_class
class << self; self; end
end
end

class PetShop < DSLThing
attr_accessor :pets, :people

def self.create(&block)
f = PetShop.new
f.class.instance_eval(&block) if block_given?
f.copyvars
return f
end

def self.pet(name, &blk)
@pets ||= Hash.new
klass = Class.new(Pet)
Object.const_set(name, klass) if not Object.const_defined?(name)
p = Object.const_get(name).new
p.name = name
p.class.class_eval(&blk) if block_given?
p.copyvars
@pets[name] = p
end
end

class Animal < DSLThing
attr_accessor :name

def initialize(name=nil)
@name = name
end
end

class Pet < Animal
def initialize(name=nil)
@name = name
super
end

def self.when_performing_tricks(&routine)
@routine = routine
end

def self.define_trick(name, &trick_definition)
singleton_class.class_eval do
define_method name, &trick_definition
end
end

def perform
puts "#{name} will now perform..."
@routine.call
puts "Let's hear some applause for #{name}!"
end

def self.sit
puts "#{name} is sitting..."
end
end

shop = PetShop.create do
pet "Toto" do
when_performing_tricks do
sit
speak
end

define_trick "speak" do
puts "Toto says 'Woof!'"
end
end

pet "Tweety" do
when_performing_tricks do
sit
speak
fly
end

define_trick "speak" do
puts "Tweety says 'I thought I saw a putty tat!"
end

define_trick "fly" do
puts "Tweety is flying..."
end
end

pet "Slugworth" do
when_performing_tricks do
sit
end
end
end

shop.pets.each_value do |pet|
pet.perform
end

Sunday, November 19, 2006

Ruby Domain Specific Languages - The Basics (Part 3)

This is another installment in a series about creating domain specific languages with Ruby. In part 1 and part 2 of this series, I created a simple Ruby DSL to describe the relationship between Pets and Persons. Now I will extend the DSL, and use some cool Ruby metaprogramming tricks to demonstrate the power and benefits of using Ruby to create an internal DSL.
First, I want to simplify the syntax for declaring things in our DSL. Getting rid of the class definition stuff can make it a lot easier for domain experts to read. A simple, cool declarative style like Rake is what we want to end up with, something like this:

person "Dorothy" do
temperament :nice
food :sunflower_seeds, :carrot_juice
end

Furthermore, I want to extend our DSL to put all of the descriptions of Pets and Persons together into a PetShop. Here is our PetShop DSL:

shop = PetShop.create do
pet "Toto" do
friend_test do |person|
true unless person.temperament == :mean
end
end

pet "Tweety" do
friend_test do |person|
person.has_food?(:sunflower_seeds)
end
end

pet "Slugworth" do
friend_test do |person|
true # I like anyone
end
end

person "Dorothy" do
temperament :nice
food :sunflower_seeds, :carrot_juice
end

person "Witch" do
temperament :mean
food :cheetos, :soda
end
end

One interesting technique used is declaring the "pet" within the "do-end" block for the "petshop" object:

shop = PetShop.create do
pet "Toto" do
friend_test do |person|
true unless person.temperament == :mean
end
end

...
end

The little bit of DSL niceness is achieved using the class_eval method. The block of code within the "do" block is called in the context of the newly created object. Here is the Ruby code that achieves this for a new Pet:

def self.pet(name, &blk)
@pets = Hash.new
p = Pet.new(name)
p.class.class_eval(&blk) if block_given?
@pets[name] = p
p.copyvars
end


Now that we have declared all of the Pets and Persons in to the context of a PetShop, we can test the relationships between Pets and Persons is a more general purpose way then in my previous post. The older, more fragile code was this:

dog = Toto.new
bird = Tweety.new
snail = Slugworth.new

person = Dorothy.new
puts "#{dog.class.name} is a friend of #{person.class.name}: #{dog.is_friend?(person)}"
puts "#{bird.class.name} is a friend of #{person.class.name}: #{bird.is_friend?(person)}"
puts "#{snail.class.name} is a friend of #{person.class.name}: #{snail.is_friend?(person)}"
puts

person = Witch.new
puts "#{dog.class.name} is a friend of #{person.class.name}: #{dog.is_friend?(person)}"
puts "#{bird.class.name} is a friend of #{person.class.name}: #{bird.is_friend?(person)}"
puts

The new, cleaner code is like this:

shop.people.each_value do person
shop.pets.each_value do pet
puts "Is #{pet.name} a friend of #{person.name}? #{pet.is_friend?(person)}"
end
end

And here is the output when we run the program:

Is Toto a friend of Witch? false
Is Slugworth a friend of Witch? true
Is Tweety a friend of Witch? false
Is Toto a friend of Dorothy? true
Is Slugworth a friend of Dorothy? true
Is Tweety a friend of Dorothy? true

We have simplified the syntax of our domain specific language, and added some additional functionality. We have also been able to reduce the amount of Ruby code required at the same time.

Here is the final version of the code:

class DSLThing
def copyvars
self.class.instance_variables.each do |var|
instance_variable_set(var, self.class.instance_variable_get(var))
end
end
end

class PetShop < DSLThing
attr_accessor :pets, :people

def self.create(&block)
f = PetShop.new
f.class.class_eval(&block) if block_given?
f.copyvars
return f
end

def self.pet(name, &blk)
@pets ||= Hash.new
p = Pet.new(name)
p.class.class_eval(&blk) if block_given?
@pets[name] = p
p.copyvars
end

def self.person(name, &blk)
@people ||= Hash.new
p = Person.new(name)
p.class.class_eval(&blk)
@people[name] = p
p.copyvars
end
end

class Animal < DSLThing
attr_accessor :name

def initialize(name=nil)
@name = name
end
end

class Person < Animal
attr_accessor :temperament

def initialize(name=nil)
super
end

def self.temperament(type)
@temperament = type
end

def self.food(*types_of_food)
@food = []
types_of_food.each do |food|
@food << food
end
end

def has_food?(type_of_food)
@food.include?(type_of_food)
end
end

class Pet < Animal
def initialize(name=nil)
super
end

def self.friend_test(&test)
@friend_test = test
end

def is_friend?(person)
@friend_test.call(person) == true
end
end

shop = PetShop.create do
pet "Toto" do
friend_test do |person|
true unless person.temperament == :mean
end
end

pet "Tweety" do
friend_test do |person|
person.has_food?(:sunflower_seeds)
end
end

pet "Slugworth" do
friend_test do |person|
true # I like anyone
end
end

person "Dorothy" do
temperament :nice
food :sunflower_seeds, :carrot_juice
end

person "Witch" do
temperament :mean
food :cheetos, :soda
end
end

shop.people.each_value do |person|
shop.pets.each_value do |pet|
puts "Is #{pet.name} a friend of #{person.name}? #{pet.is_friend?(person)}"
end
end

Wednesday, November 08, 2006

Ruby Domain Specific Languages - The Basics (Part 2)

Previously, I was exploring the basics of DSL creation using Ruby. This post continues sharing my lessons learned while developing a prototype of a domain specific language in the mortgage industry. Since I cannot share the actual code itself belonging to my client, I will continue to extract the useful concepts into these simple examples.

Last time we created a simple Animal DSL class. The important part of that class is this bit that makes sure our DSL like syntax works for the declarative methods:

class Animal
attr_accessor :number_of_legs

def self.number_of_legs(number_of_legs)
@number_of_legs = number_of_legs
end

def initialize
self.class.instance_variables.each do |var|
instance_variable_set(var, self.class.instance_variable_get(var))
end
end
end

Now we will create a Person class that can interact with the Animals. Each Person will have a temperament (either mean or nice), and will be carrying some kind of food in their pocket to feed their pet. We will define people using our DSL as follows:


class Dorothy < Person
temperament :nice
food :sunflower_seeds, :carrot_juice
end

class Witch < Person
temperament :mean
food :cheetos, :soda
end


The implementation is very similar to the basic Animal class

class Person < Animal
attr_accessor :temperament

def self.temperament(type)
@temperament = type
end

def self.food(*types_of_food)
@food ||= []
types_of_food.each do |food|
@food << food
end
end

def has_food?(type_of_food)
@food.include?(type_of_food)
end
end


Since we are working with an array, the self.food method has a variable number of parameters, represented using the asterisk in front of the parameters list. The cool little idiom @food ||= [] returns with the current @food variable, or if it is nil, returns an empty array. There is also a has_food? method to tell us if a person is carrying a certain type of food.

Now let us introduce the Pet. We will use the power of Ruby blocks to tell each pet the rules about when it likes a person, or not. Here is the DSL we want to use for the Pets:

class Toto < Pet
friend_test do |person|
true unless person.temperament == :mean # I like anyone who is not mean
end

end

class Tweety < Pet
friend_test do |person|
true if person.has_food?(:sunflower_seeds) # I like anyone who has sunflower seeds
end
end

class Slugworth < Pet
friend_test do |person|
true # I like anyone
end
end


And now the implementation of the Pet class:

class Pet < Animal
def self.friend_test(&test)
@friend_test = test
end

def is_friend?(person)
@friend_test.call(person) == true
end
end


The friend_test method stores a block containing the test for that Pet, and the is_friend? method tests to see if a Pet will be friendly toward a particular Person.
Last, we put it all together with some simple code to display the interactions between the People and the Pets:

dog = Toto.new
bird = Tweety.new
snail = Slugworth.new

person = Dorothy.new
puts "#{dog.class.name} is a friend of #{person.class.name}: #{dog.is_friend?(person)}"
puts "#{bird.class.name} is a friend of #{person.class.name}: #{bird.is_friend?(person)}"
puts "#{snail.class.name} is a friend of #{person.class.name}: #{snail.is_friend?(person)}"
puts

person = Witch.new
puts "#{dog.class.name} is a friend of #{person.class.name}: #{dog.is_friend?(person)}"
puts "#{bird.class.name} is a friend of #{person.class.name}: #{bird.is_friend?(person)}"
puts "#{snail.class.name} is a friend of #{person.class.name}: #{snail.is_friend?(person)}"


The result of running this code is:

Toto is a friend of Dorothy: true
Tweety is a friend of Dorothy: true
Slugworth is a friend of Dorothy: true

Toto is a friend of Witch: false
Tweety is a friend of Witch: false
Slugworth is a friend of Witch: true

Using this simple Ruby DSL approach, it is very easy to add a new Person or Pet, just by entering the rules that define its behavior. As the objects in a system become more complex, one of the best ways to manage that complexity is to create an abstraction that hides the ugly bits.

Here is the full code for this example:

class Animal
attr_accessor :number_of_legs

def self.number_of_legs(number_of_legs)
@number_of_legs = number_of_legs
end

def initialize
self.class.instance_variables.each do |var|
instance_variable_set(var, self.class.instance_variable_get(var))
end
end
end

class Person < Animal
attr_accessor :temperament

def self.temperament(type)
@temperament = type
end

def self.food(*types_of_food)
@food ||= []
types_of_food.each do |food|
@food << food
end
end

def has_food?(type_of_food)
@food.include?(type_of_food)
end
end

class Pet < Animal
def self.friend_test(&test)
@friend_test = test
end

def is_friend?(person)
@friend_test.call(person) == true
end
end


class Toto < Pet
friend_test do |person|
true unless person.temperament == :mean
end

end

class Tweety < Pet
friend_test do |person|
person.has_food?(:sunflower_seeds)
end
end

class Slugworth < Pet
friend_test do |person|
true
end
end

class Dorothy < Person
temperament :nice
food :sunflower_seeds, :carrot_juice
end

class Witch < Person
temperament :mean
food :cheetos, :soda
end


dog = Toto.new
bird = Tweety.new
snail = Slugworth.new

person = Dorothy.new
puts "#{dog.class.name} is a friend of #{person.class.name}: #{dog.is_friend?(person)}"
puts "#{bird.class.name} is a friend of #{person.class.name}: #{bird.is_friend?(person)}"
puts "#{snail.class.name} is a friend of #{person.class.name}: #{snail.is_friend?(person)}"
puts

person = Witch.new
puts "#{dog.class.name} is a friend of #{person.class.name}: #{dog.is_friend?(person)}"
puts "#{bird.class.name} is a friend of #{person.class.name}: #{bird.is_friend?(person)}"
puts "#{snail.class.name} is a friend of #{person.class.name}: #{snail.is_friend?(person)}"

Saturday, November 04, 2006

Ruby Domain Specific Languages - The Basics (Part 1)


I have been working on a prototype of a Ruby domain specific language for one of my clients, a very large financial services company. I have learned a whole bunch of really interesting lessons, which I will share over a short series of posts as I make more progress.

The first thing I tried to do was create a basic bit of DSL tastiness like this:


class Dog < Animal
number_of_legs 4
end

class Bird < Animal
number_of_legs 2
end

class Snail < Animal
number_of_legs 0
end



My first implementation of the Animal class looked like this:


class Animal
attr_accessor :number_of_legs

def self.number_of_legs(number_of_legs)
@number_of_legs = number_of_legs
end
end




Just looking at this bit of code, it seems like it should work. However, when trying this, it doesn't work as expected.


pet = Dog.new
p pet.number_of_legs # prints nil




In Ruby EVERYTHING is an object. This includes the class objects themselves, not just the instances of class objects! This means that when the number_of_legs method is called, it is actually being called before the actual instance object itself has been created. We need to get to our instance variable, and one way is to copy the class-level instance variable to the instance itself when the actual instance is being initialized like this:


class Animal
attr_accessor :number_of_legs

def self.number_of_legs(number_of_legs)
@number_of_legs = number_of_legs
end

def initialize
instance_variable_set("@number_of_legs", self.class.instance_variable_get("@number_of_legs"))
end
end


Now the class works as expected:


pet = Dog.new
p pet.number_of_legs # prints 4




As you add new things to your DSL, having to copy each variable manually is boring. The final change is to copy every class instance variable automatically at initialization, as follows:


class Animal
attr_accessor :number_of_legs

def self.number_of_legs(number_of_legs)
@number_of_legs = number_of_legs
end

def initialize
self.class.instance_variables.each do var
instance_variable_set(var, self.class.instance_variable_get(var))
end
end
end


This is a lot cleaner, and is much more maintainable code.