A Block Costume #
We follow Block, who goes into a cloak room and emerges with a change of fashion. A black hat for its dark magic, a dagger for its villianous theft of an instance’s binding, and a glimmering red ring for its metaprogramming fu.
Hey, would you look at that? Blocks actually look pretty good as unbound eigenmethods!
The Cloaker
Let’s say we have an HTML
class for building HTML. Here’s the short dressing method:
class HTML def cloaker &blk (class << self; self; end).class_eval do define_method :cloaker_, &blk meth = instance_method( :cloaker_ ) remove_method :cloaker_ meth end end end
Giving Parents to the Kids
You’re probably used to seeing code by now, in Rails and Builder and other meta-heavy libs, which passes a single class into a block for tooling with.
For example, in Builder, you’ll make an XML file like this:
xml = Builder::XmlMarkup.new :indent => 2 xml.person do |b| b.name("Jim") b.phone("555-1234") end
As you get deeper in the XML file, you pass down each element to its kids using a block variable. In the above example: b
.
Now, I ask, what is self
in the above example? Well, it’s set to whatever instance is self
in the encompassing scope.
Self Theft
Let’s use a cloak so our block can steal the soul of self
.
class HTML TAGS = [:html, :head, :title, :body, :h1, :h2, :h3, :div] def initialize &blk; html &blk end def method_missing tag, text = nil, &blk raise NoMethodError, "No tag `#{tag}'" unless TAGS.include? tag print "<#{tag}>#{text}" if blk cloaker(&blk).bind(self).call end print "</#{tag}>" end end
Now when we attach the block, you’ll be able to run methods freely inside the block, as if you were inside a singleton instance method.
title = "My Love is Like a LaserJet Set at 300 DPI" sub = "Poetry Selections from Kinko's Employees" HTML.new do head do title title end body do h1 title h2 sub div "Oh, Toner! How it doth trickle down the arms!" end end
The evil part is how the namespaces wash together. Check out that fifth line. The title
method and title
var cozy up just fine. A huge problem with this code is that methods in the block’s scope still take precedence. (Try adding: def title; end
right before the first line.)
You can use self
explicitly in those cases. Anyway, the full test script is here. (Hack inspired by override_method.)
Update: mel has noted that most of what cloaker does can be done with instance_eval. MenTaL has noted that 1.9’s instance_exec will do it all.
Jim Weirich
As you get deeper in the XML file, you pass down each element to its kids using a block variable. In the above example: b.
Actually, you only need it in the top level. The ‘b’ is in scope so that nested elements just refer to it. (and perhaps that’s what you meant).
Dan Amelang and I worked out a method of doing something similar to what you did, but it involved a whole lot more code. I will have to study your solution in more depth.
why
I’m not sure if this is better. I really can’t say. It might be too smart. Sometimes that block argument seems redundant (like Python using self in its methods). But what if you need to interleave blocks that keep stealing the binding? Then no one gets anything done. Hard choice.
crzwdjk
That’s… amazing and confusing and sort of inside out. But wonderful magic, and I’m starting to see more and more why ruby’s meta-magic is at least as powerful as lisp’s macro-magic.
crzwdjk
That’s… amazing and confusing and sort of inside out. But wonderful magic, and I’m starting to see more and more why ruby’s meta-magic is at least as powerful as lisp’s macro-magic.
trans
Ooo, nice generalization of “Cloaker Pattern” ;) I’ve utilized similiar bahavior before, now I’ll drop cloaker into Facets and and have a standard method to call on to do it. Great idea. Thanks.
Danno
That is highly slick.
I only wish that I could come upon opprotunities to apply such slickness.
Ben
You filthy crazy man! That’s EVIL .
EEEEEVIL .
mel
Maybe I am missing something, but it seems that you just reimplemented instance_eval:
Just replace:
cloaker(&blk).bind(self).call
with
instance_eval &blk
and the result is exactly the same.
why
Oh, hey, you’re right! I should have used a different example which gave the block some other trivial arguments.
Like maybe a better example would be iterating through streaming HTTP . Assume
out
is an open pipe.In the above,
bytes_read
andcontent_length
are are instance methods.But I don’t know you could do the same with
instance_eval
but use two blocks:Yeah. These things.
MenTaLguY
mel: well, yes, if your block doesn’t take any arguments. If you need to pass arguments to it, though, then cloaker’s the only way to go until Ruby 1.9 (which adds an
Object#instance_exec
).MenTaLguY
why: errr… wait. instance_eval lets you pass an argument to the block?
lisp bigot
I’m starting to see more and more why ruby’s meta-magic is at least as powerful as lisp’s macro-magic.
Not until cloaker() can rewrite the code in the block before running it… keep diggin’...
why
Whoops, sorry MenTal, that should be:
mel
MenTaLguY: Yes, I am aware of that (and instance_exec). (But to be honest: I didn’t realize that cloaker is that workaround, until I read your post)
aberant
great, now all this x-mas i’ll be on my laptop trying to dissect the wizardry going on here. 8)
trans
Actually is it neccessary for the cloaker to be eigen? Since it’s being removed why not just define it one the class of the object? (Which is how I had gone about in the past).
MenTaLguY
trans: there’s less chance of stomping on anything important (i.e. an existing
cloaker_
method in that object’s class).Anyway, the call is really only applicable for that one object. Why not eigen it?
Comments are closed for this entry.