This post is part of a series of reviews on the book Design Patterns in Ruby. Check out the Introduction post for a full table of contents along with some generic principles regarding Design Patterns.

Domain Specific Languanges, DSLs from now on, are languages that are meant (as the name says) to serve a specific domain. In general, people don’t understand programming languages, but what if we could use Ruby to create a special language that other people could understand, and more importantly, use without much dificulty?

As a quick example, RSpec, a testing framework, is a DSL by itself: its domain is testing, and when you look at an RSpec file, it doesn’t look like some complicated programming language, it looks alot like plain english, and most importantly, it makes perfect sense for testers. Here’s an example of RSpec taken directly from their homepage:

describe Bowling, "#score" do
  it "returns 0 for all gutter game" do
    bowling = Bowling.new
    20.times { bowling.hit(0) }
    bowling.score.should == 0
  end
end

DSLs, in a way, have the purpose of making a certain program make sense to non-programmers, and due to Ruby's extremely light syntax rules, we can make that happen.

For the sake of simplicity, let's code a small Agenda DSL that let's someone plan their day and then get a pretty format of the whole schedule for the day. The best way to start is to actually code what you would like to see as the result, and then work your way back to make it all work, so something pretty neat for an Agenda could be like this:

Agenda.for :today do |schedule|
	schedule.this :morning do |add|
		add.task 'Take kids to school'
		add.reminder 'There is traffic on the highway'
		add.task 'Go to work'
	end
	schedule.this :afternoon do |add|
		add.task 'Go back to work'
		add.reminder 'Bring home dinner'
	end
	schedule.this :night do |add|
		add.task 'Work on side project'
		add.reminder'Dentist tomorrow'
	end
end

As you can see this is perfect readable code, you can actually follow a line of thought by reading through it. We could do alot better to make it even more syntax friendly, but for this example that will have to do. This DSL would ideally generate something like:

Day: 25/07/2010
------Morning schedule-----
REMEMBER:
**There is traffic on the highway**
Tasks:
 - Take kids to school
 - Go to work
------Afternoon schedule-----
REMEMBER:
**Bring home dinner**
Tasks:
 - Go back to work
------Night schedule-----
REMEMBER:
**Dentist tomorrow**
Tasks:
 - Work on side project

To generate that DSL is rather simple: the trick is to make use of alot of code blocks and code external methods (the ones a user will use) as much syntax friendly as possible, so there's really not much of a formula to it like the common GoF patterns, you just have to code putting yourself in the place of a user who knows nothing about programming but wants to get something done. Anyway, if you studied your Ruby, you should have no trouble understanding this code:

class Agenda
	class << self
		alias_method :for, :new
	end
	
	def initialize(day)
		@tasks  = {:morning => '', :afternoon => '', :night => ''}
		@reminders = {:morning => '', :afternoon => '', :night => ''}
		puts "Day: #{Date.today.strftime("%d/%m/%Y")}" if day == :today
		yield(self)
	end
	
	def add_task(name, day_time)
		@tasks[day_time] += " - #{name}\n"
	end
	
	def add_reminder(name, day_time)
		@reminders[day_time] += "**#{name}**\n"
	end
	
	def this(daytime)
		puts "------#{daytime.to_s.capitalize} schedule-----\n"
		yield(DayTime.new(daytime, self))
		puts "REMEMBER:\n"
		puts @reminders[daytime]
		puts "Tasks:\n"
		puts @tasks[daytime]
	end
end

class DayTime
	def initialize(name, agenda)
		@name = name
		@agenda = agenda
	end
	
	def task(task)
		@agenda.add_task(task, @name)
	end
	
	def reminder(reminder)
		@agenda.add_reminder(reminder, @name)
	end
end
About these ads