That said, what I wanted to come up with was a way to define methods such that you received both the arguments to the method and the block given to the method completely unevaluated. That is, you'll be given an array of strings representing the arguments and one large string representing the block passed to the marco. You are then be free to do whatever you'd like with the arguments and the body. This, to me, captures at least the essence of macros (yes, I'm probably wrong). To do so, I've whipped up a gem called def_macro.
Below I'll show you some samples using the gem to define two classic Lisp macros: with and loop. The with macro basically gives a block access to some local variables while executing. The loop macro just loops over some code n times (the loop macro I'm describing here is WAY simplified from the true Lisp loop macro). Anyway, I'll show the examples in two stages: defining the macro using def_macro and using the defined macro.
The with macro:
def_macro :with do |args,body|
eval args.first
eval body
end
with proc {a = 1; b = 2 } do
puts a + b
end # => 3Notice in this case I'm only evaluating the first argument (there's only one). By doing so, I've set my local variables. I'm then free to evaluate the body string. Everything works as expected and we see 3 printed out. Notice that if I'm passing code as arguments, they must be inside of a proc or lambda. Ruby forces us to do this if we don't want the code evaluated immediately.
The simplified loop macro is even easier to implement. Take a look:
The loop macro:
def_macro :loop do |args,body|
(eval args.first).times do
eval body
end
end
loop 2 do
puts "hi"
end # => "hi" "hi"Notice this time that it's not necessary to wrap the argument in a proc as it doesn't matter when the number 2 is evaluated.
You can download the gem in two ways:
- rubyforge: sudo gem install defmacro
- github: sudo gem install dfg59-defmacro --source=http://gems.github.com
The source is on github at http://github.com/dfg59/def_macro/tree/master
Update: Fixed spelling of macro (thanks to Nwallins on reddit)
2 comments:
As you've noted, this unfortunately doesn't give you the full power to define new syntax constructs with new eval rules, but I guess it could be useful in a few cases.
Also, the example "with" macro takes advantage of a bug in Ruby 1.8 where block-local variables outlive the scope of the block, so this won't work in 1.9.
Speaking of Ruby 1.9, I understand that parsing a block or proc into sexps (and from there to ruby in Ruby2Ruby) goes away in Ruby 1.9 as well.
On the other hand, Rubinius is working hard to support sexps being available at run time, so perhaps the syntactic abstraction progress will move away from MRI.
Post a Comment