hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

A Block Costume #

by why in inspect

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.

said on 23 Dec 2005 at 13:25

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.

said on 23 Dec 2005 at 13:39

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.

said on 23 Dec 2005 at 16:07

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.

said on 23 Dec 2005 at 16:07

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.

said on 23 Dec 2005 at 16:22

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.

said on 23 Dec 2005 at 17:10

That is highly slick.

I only wish that I could come upon opprotunities to apply such slickness.

said on 23 Dec 2005 at 17:40

You filthy crazy man! That’s EVIL .

EEEEEVIL .

said on 23 Dec 2005 at 19:43

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.

said on 23 Dec 2005 at 20:45

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.

 URI.get( "http://example.mp3", :bufsize => 8192 ) do |s|
   out << s
   print "%02d" % [(bytes_read / content_length)*100]
 end

In the above, bytes_read and content_length are are instance methods.

But I don’t know you could do the same with instance_eval but use two blocks:

 URI.get( "http://example.mp3" ) do |u|
   u.stream( :bufsize => 8192 ) do |s|
     out << s
     print "%02d" % [(u.bytes_read / u.content_length)*100]
   end
 end

Yeah. These things.

said on 23 Dec 2005 at 20:46

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).

said on 23 Dec 2005 at 20:49

why: errr… wait. instance_eval lets you pass an argument to the block?

said on 23 Dec 2005 at 21:09

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’...

said on 23 Dec 2005 at 21:34

Whoops, sorry MenTal, that should be:

 URI.get( "http://example.mp3" ) do
   stream( :bufsize => 8192 ) do |s|
     out << s
     print "%02d" % [(bytes_read / content_length)*100]
   end
 end
said on 23 Dec 2005 at 21:35

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)

said on 23 Dec 2005 at 21:55

great, now all this x-mas i’ll be on my laptop trying to dissect the wizardry going on here. 8)

said on 24 Dec 2005 at 01:24

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).

said on 25 Dec 2005 at 20:20

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.