Writing Domain Specific Languages (DSLs) with Ruby

August 03, 2009

A lot of people say that Ruby is a great languages for writing Domain Specific Languages (DSLs). A DSL is a highly abstracted programming language that gives you a natural and intuitive way to deal with a specific logical domain. They can serve as easy flexible APIs for programmers or enable clients to have control over the way a system deals with their business logic. There are lots of examples of DSLs in the Ruby world. Capistrano, RSpec, Thinking Sphinx, Rails’ Routing, just to name a few.

In this post I’ll look at some of the most common ways to create a DSL in Ruby.

A DSL for Defending Medieval Castles

A new client, Medieval Guards, Inc. specializes in guarding medieval castles and fighting off Barbarian attackers. They need some software written that will help them create battle plans and coordinate the castles’ defenses. But the Barbarians are always changing their tactics, so the system needs to be able to change its defense strategy flexibly. Each castle’s commander needs to be able to define new battle plans as battle conditions change. Perfect use for a DSL.

The Final Product

Let’s take a look at what kind of syntax we’d like our commander to write to define a new battle plan.

commander.define_battle_plan do
  fire cannon if enemy_approaching?
  unless enemy_neutralized?
    sound_alarm
    fire crossbow
  end
  if enemy_approaching?
    prepare boiling_oil
    raise_drawbridge
    fire flaming_catapult
  end
  if enemy_approaching?
    prepare evacuation
    pour boiling_oil
  end
  if enemy_neutralized?
    lower_drawbridge
    prepare paperwork
  end
end

You can see here that even though this is pure Ruby code, it’s pretty easy for a non-programming commander to see what’s going on here. If the Barbarians all get scooters, and the castle needs to prepare boiling oil as soon as their sighted, it wouldn’t be hard to make that change.

How Does It Work?

Let’s assume that the defense of castles is handled directly by castle guards. In our application part of the Guard class we’ve implemented looks like this:

class Guard < Warrior
  def fire(weapon)
    weapon.load unless weapon.loaded?
    weapon.aim
    weapon.fire
  end

  def sound_alarm
    horn.sound
  end

  def raise_drawbridge
    drawbridge.control(:up)
  end

  def lower_drawbridge
    drawbridge.control(:down)
  end

  def prepare(recipe)
    recipe.follow
  end

  def enemy_approaching?
    telescope.enemy_visible? and not telescope.enemy_dead?
  end

  def enemy_neutralized?
    not enemy_approaching?
  end

  def boiling_oil
    define_recipe :boiling_oil do
      fire.stoke
      pots.each{|pot| pot.fill :oil}
    end
  end

  def evacuation
    define_recipe :evacuation do
      gather_women_and_children
      escape_through_tunnels
    end
  end
end

You can see that the methods on Guard are the same as the vocaulary that the commander is using in our define_battle_plan DSL block.

We create a Commander class which is responsible for defining a battle plan and triggering the guards to execute it.

Defining the Battle Plan

class Commander
  attr_accessor :plan
  def define_battle_plan(&plan)
    self.plan = plan
  end

  # ...
end

Here we have a method called define_battle_plan that takes a block argument called plan. Notice that when we call define_battle_plan it never executes the block. Instead it just tucks the code away in an attribute until we need to fight off an attack.

Executing the Battle Plan


class Commander
  def defend_castle!
    guard_on_duty.instance_eval &plan
  end
end

There’s another method on our commander class that will cause our battle plan to be run. When we call defend_castle! the plan we saved is executed, but not by the commander class. Instead the code is run in the context of a Guard instance (the guard_on_duty). Within the battle plan block, self will refer to the defending Guard giving us easy access to all of the instances methods.

Another Approach

There are a few ways besides instance_eval to get an nice DSL-y syntax. Within Rails it’s popular to pass the object into the block to get a syntax like this:

commander.define_battle_plan do |guard|
  guard.fire cannon if guard.enemy_approaching?
end

To get this syntax we would change our defend_castle! method to look like this:

class Commander
  def defend_castle!
    plan.call guard_on_duty
  end
end

One advantage (or disadvantage) to this approach is that within the plan block self still refers to the context it was defined in, in this case the Commander instance. This means we can call methods from the calling class as well. Imagine the Commander had a notify_king method.

commander.define_battle_plan do |guard|
  guard.fire cannon if guard.enemy_approaching?
  notify_king unless guard.enemy_neutralized?
end

More Complex Approaches

There are a variety of more complicated ways to handle which object’s methods get called in your DSL. You can use method_missing, Forwardable, or Delegate to create a chain of responders. We could have the Guard handle the action if he has a corresponding method, and the Commander handle it otherwise. _why gets into some other possibilities here.

Comments

  1. Ian Smith-Heisters
    Ian Smith-Heisters August 05, 2009 @ 09:37 AM

    I’m anti-instance_eval these days. See http://gist.github.com/154550 for my favorite du-jour.

  2. Sam Goldstein
    Sam Goldstein August 05, 2009 @ 05:59 PM

    @Ian – This is a pretty interesting approach, which I tried to allude to at the end of the article. It could be criticized as being too magical, but in a way that’s the whole point of DSLs. Whether it’s worth the additional complexity of setting up a chain of responders, seems to depend on the nature of your application.

    If you want one DSL to be accessible in a bunch of different classes (and still have easy access to their native methods) your gist seems like a great way to go. For many cases though it seems overly convoluted.