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_attr
looks at the name of the attempted method call (:rewrite
), yanks out@rewrite
from 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_attr
above for the specifics.MenTaLguY
Daniel Berger: It is true that much of this could (should, perhaps) be done in initialize, but
yield self
is 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.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:
And 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_eval
thing 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
@__obj
and@#{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,
@__obj
is an ordinary instance variable. No magic in itself."@#{m}"
is basically the same as"@" + m.to_s
MenTaLguY
Also, I have been subconsciously ripping off why’s metaid. Wondered why the name
MetaMaid
seemed so natural.Object#meta_eval
indeed. My only contribution is the multi-level recursion and maybe theeigen_accessor
business.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_make
code 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
mo
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.