One Small Flask of Metaprogramming Elixir #
We could make metaprogramming easier for the common streetsfolk. I’m not giving a triumphant answer here. I’m just wondering what else you knobbly ducks out there have done like this.
So you have this class which defines a structure for scripts within a larger program (MouseHole in this case):
class UserScript
attr_accessor :title, :description, :version, :mount,
:register_uri, :rewrite
def initialize
@register_uri, @rewrite = [], {}
end
end
Nothing special. But the initialize does tell us that register_uri and rewrite are an array and a hash, respectively.
Use a method_missing to set the accessors based on their defaults in initialize:
meta_make(UserScript) do
title "MouseCommand"
description "A-wiki-and-a-shell-in-one."
version "2.0"
mount :cmd do
%{<html> .. </html>}
end
rewrite HTML, XML do
document.change_someways!
end
rewrite CSS do
document.gsub! '.title', '.titleSUPERIOR'
end
# allow methods to be defined
def to_gravy; end
end
So, since rewrite was set up in initialize as a hash, the definition above will result in:
@title = "MouseCommand"
@description = "A-wiki-and-a-shell-in-one."
@version = "2.0"
@mount = [:cmd, #<Proc:903e>],
@rewrite = {
HTML => #<Proc:0xf140>,
XML => #<Proc:0xf140>,
CSS => #<Proc:0x34a3>
}
It’s very Railsish, but as people are now so accustomed to that syntax, it could ease transition of Railsers to your project, whatever it be.
Here’s the meta_make method:
def meta_make(klass, &blk)
o = klass.new
(class << o; self; end).instance_eval { @__obj = o }
class << o
def self.method_missing(m, *args, &blk)
set_attr(m, *args, &blk)
end
def self.set_attr(m, *args, &blk)
if blk
args << nil if args.empty?
args << blk
end
v = @__obj.instance_variable_get("@#{m}")
if v.respond_to? :to_ary
(v = v.to_ary).push args
elsif v.respond_to? :to_hash
v = v.to_hash
while args.length > 1
v[args.unshift] = args.last
end
elsif args.length <= 1
v = args.first
else
v = args
end
@__obj.instance_variable_set("@#{m}", v)
end
end
(class << o; self; end).class_eval &blk
o
end


rue
Brilliant. (Sorry, I am all out of zany & wacky.)
gmosx
Very cool :)
Danno
I… have no idea what to add.
Quick question though: Any reason you decided to change up between class_eval and instance_eval when calling against the eigenclass?
nedric
Hmmm, it looks like @register_uri in the UserScript class gets changed to @mount in method_missing… typo?
riffraff
mh.. is’nt there something like this in the stdlib ? IIRC Tk does named arguments this way in ruby, since we can’t mimic the tcl syntax better
aberant
i’m now entirely convinced that _why is a ruby wizzard sent from the future to save mankind..
ouch
It makes my head hurt. Can anyone explain in really simple words what this is about.
Daniel Berger
Using method_missing is a good way to create difficult to debug code. It’s a last resort, not a first resort.
Use “yield self if block_given?” as one of the first things in initialize, then just use ”||=” within initialize to set things that need setting. Easier to read. Faster, too, I’ll bet.
MenTaLguY
aberant: yes, it is about time we developed a mythology around him.
ouch: here’s the basics:
meta_make(UserScript)creates an instance of UserScript and execs the attach’d block in the context of its eigenclass (sort of as if it wereclass << UserScript.new).The key is that it slips some additional bits into the eigenclass before doing so. That call to
rewrite XML, HTML, for example? It hits the eigenclass’smethod_missing, which has been made basically just a call toset_attr.set_attrlooks at the name of the attempted method call (:rewrite), yanks out@rewritefrom the object (thoughtfully made available via__obj), sees it’s a hash, and merges in its parameters accordingly (each arg becomes a key, the block becomes the value).We’ve got different rules for instance variables which are arrays and so forth, but that’s the basic idea. See the definition of
set_attrabove for the specifics.MenTaLguY
Daniel Berger: It is true that much of this could (should, perhaps) be done in initialize, but
yield selfis quite different to instance_exec’ing the block on the eigenclass.Perhaps, though, that makes more sense in the context of MouseHole, where UserScript is sort of a factory for one-off singletons rather than a class being used the normal way.
For most purposes you could probably squish this down into less meta-ness.
MenTaLguY
Hmm, and it is very meta, isn’t it? We’re adding things to the eigenclass’s own eigenclass here.
Daniel Berger
MenTaLguY
This gives me an idea. We can secret our instance variables in in our eigenclass, can’t we?
Maybe that is useful for when we are writing something meta and want to be all “hands off my instance variables”.
MenTaLguY
Daniel: with make_meta that stuff is added to an instance, not to the class.
MenTaLguY
make_meta? meta_make, rather. The meta method, for mouseHole.
MenTaLguY
Actually… if we’re putting stuff in eigenland, why use instance variables a’tall?
MenTaLguY
Hey, since we can have eigenclasses of eigenclasses, we can do this:
How meta can you go?
Object.new.meta(5)=> #<Class:#<Class:#<Class:#<Class:#<Class:#<Object:0x3247b10>>>>>>why
To get around the
method_missing/ debug problem, I think I’d raise a noodle if there’s no setter.raise ProblemChild unless respond_to? "#{m}="In
set_attr. Dan, we’re trying to make simple scripts pretty for beginners, not reach some moral/ethical crossroad.MenTaLguY
Okay soo… to bring this back on topic, here’s what things might look like doing in initialize. Let’s give ourselves a meta_eval first, though.
So, then…
Then I think you could do like:
class UserScript < MetaMaid attr_accessor :title, :description, :version, :mount, :register_uri, :rewrite def initialize( &blk ) @register_uri, @rewrite = [], {} super( &blk ) end endAnd then turn out your scripts like:
I don’t know. You could probably make MetaMaid a module even.
MenTaLguY
Er, the “back on topic” line was aimed at myself, since I’ve been astronauting the eigenspace when we should instead be looking making things friendly for the newbies.
Meta-newbies might like this
Object#meta_evalthing though.Adharma
I’m trying to wrap my head around this code, but coming up a bit short. Some of the syntax is chewy. And heck, I’m new to this ruby thing anyhoo. So…
What is going on with
@__objand@#{m}? Meaning mostly the syntax.And in…
I understand that “MouseCommand” is getting assigned to title, but why? how?
Sorry if my questions are a bit simpleton, but I feel I am on there verge of understanding the concepts, but I don’t quite have the syntax/idiom down. Any help would be greatfully appreciated.
-cheers
MenTaLguY
Syntactically,
@__objis an ordinary instance variable. No magic in itself."@#{m}"is basically the same as"@" + m.to_sMenTaLguY
Also, I have been subconsciously ripping off why’s metaid. Wondered why the name
MetaMaidseemed so natural.Object#meta_evalindeed. My only contribution is the multi-level recursion and maybe theeigen_accessorbusiness.Adharma
Ok, then why the double underscore? That spurs me to find meaning there. If this is essentially a primer for nubies to understand the concepts of metaprogramming, then I have a few observations/questions. Most of the Ruby code I have seen is very human readable with little short cuts in naming and when illustrating concepts syntactical shortcuts are for the most part left out. So, I might rename some of your variables such as o to eigen_object or v to eigen_instance or m to eigen_setter. I like the code, it tickles my thinking parts, especially those that are in love with self reference and recursion. It’s really exciting and I want to wrap my noggin around as soon as possible so I might add to the dialog…
Adharma
Oh MenTaLguY, and at some point in my mental process I apparently reascribed authorship of the code to you. Bad pointer arithmatic will get you every time.
why
Adharma: This isn’t very good code for teaching beginners how write metaprogramming mysteries. It is a bit of code to make use of, though. If you understand the first three bits of code in the post above, then you can probably use the fourth without totally digesting it.
The
meta_makecode is guts code. It looks like guts and it is.The double-underscore is just stupid. To prevent clash. I’m actually hoping me or anyone will clean up the stuff. I should start working on getting this post off the front page.
Danno
_why, your code is showing.
unshift inside of the while loop in meta_make will never cause the array to shrink. Simple typo.
As for avoiding method_missing, I think I have a solution (it uses a string eval though, so I’m like “Blaaaah”)
asno
def meta_make(klass, &blk) some_class = klass.new (class << some_class; self; end).instance_eval { @whys_temp_some_class = some_class } class << some_class # Gets called when there is no method of 'name' def self.method_missing(name, *args, &blk) set_attr(name, *args, &blk) end def self.set_attr(method_name, *args, &blk) if blk args << nil if args.empty? args << blk end our_new_var = @whys_temp_some_class.instance_variable_get("@#{method_name}") if our_new_var.respond_to? :to_ary (our_new_var = our_new_var.to_ary).push args elsif our_new_var.respond_to? :to_hash our_new_var = our_new_var.to_hash while args.length > 1 our_new_var[args.unshift] = args.last end elsif args.length <= 1 our_new_var = args.first else our_new_var = args end @whys_temp_some_class.instance_variable_set("@#{method_name}", our_new_var) end end (class << some_class; self; end).class_eval &blk some_class endmo
Ruby beginners should look here: pleac.sf.net or here: www.rubyquiz.com.
Adharma
Thanks a bazillion,asno. This gives me a bit more to grip up on. I’ll be playing with it for the next few weeks when I head back to New Orleans.
Comments are closed for this entry.