Tuesday, April 24, 2007

Metaprogramming and DSL Resources?

As a newcomer to Ruby and metaprogramming, I'm still looking for good resources on this and the art of creating DSLs.

I'm picking up a little here and there by reading blog posts and articles like this one on InfoQ, as well as pouring over code from Rails and other Ruby projects, but it seems like there would be a market for a book written by someone who has done a lot in this area.

p.s. Here's a modification of an implementation from the aforementioned article that supports passing in a lambda expression, a block, or both. I suspect there may be better ways to do some of this and would love to hear it. Regardless, I'm continually astounded by the power of mixins and the reflection capabilities built into Ruby.
module Properties

def self.extended(base)
base.class_eval do
def fire_event_for(sym, arg)
@listener[sym].each {|l| l.call(arg) }
end
end
end

def property(sym, predicate=nil, &block)
define_method(sym) do
instance_variable_get("@#{sym}")
end

define_method("#{sym}=") do |arg|
if block_given? and predicate
return if !predicate.call(arg) and !block.call(arg)
elsif predicate
return if !predicate.call(arg)
elsif block_given?
return if !block.call(arg)
end
instance_variable_set("@#{sym}", arg)
fire_event_for(sym, arg)
end

define_method("add_#{sym}_listener") do |x|
@listener ||= {}
@listener[sym] ||= []
@listener[sym] << x
end

define_method("remove_#{sym}_listener") do |x|
@listener[sym].delete_at(x)
end
end

def is?(test)
lambda {|val| test === val }
end

def includes?(*test)
lambda {|val| test.include? val }
end
end

class CruiseShip
extend Properties

# property :direction
# property :direction, includes?('north', 'south', 'east', 'west')
property :speed, is?(0..300)
property(:direction, includes?('north', 'south', 'east', 'west')) {|v| v == 'thataway' }
# property(:speed) {|v| v >= 0 && v <= 300 }
end

h = CruiseShip.new

h.add_direction_listener(lambda {|x| puts "Oy... someone changed the direction to #{x}"})
h.direction = "north"

h.add_speed_listener(lambda {|x| puts "Oy... someone changed the speed to #{x}"})
h.add_speed_listener(lambda {|x| puts "Yo, dude... someone changed the speed to #{x}"})

h.speed = 200
h.speed = 300
h.speed = 301
h.speed = -1

h.direction = "south"

puts h.direction
puts h.speed

h.remove_speed_listener(1)

h.speed = 200
h.speed = 350

h.direction = "thataway"
h.direction = "whatever"

puts h.direction
puts h.speed
Output:
Oy... someone changed the direction to north
Oy... someone changed the speed to 200
Yo, dude... someone changed the speed to 200
Oy... someone changed the speed to 300
Yo, dude... someone changed the speed to 300
Oy... someone changed the direction to south
south
300
Oy... someone changed the speed to 200
Oy... someone changed the direction to thataway
thataway
200

Labels:

0 Comments:

Post a Comment

<< Home