Playing with Proc#bind
When I write internal DSLs in Ruby, I tend to do so by leveraging Object#instance_eval. #instance_eval is powerful because it’s yet another way that you can repoint self in Ruby. Object#instance_eval executes its supplied Proc in the context of the calling Object Below is a simple example.
def car(&block)
# self is "main" (an Object) here
c = Car.new
# self refers to Car instance when Proc is evaluated on the line below
c.instanceeval(&block) if blockgiven?
c
end
class Car
def make(v); @make = v; end
def model(v); @model = v; end
def engine(v); @engine = v; end
end
car do make "Ford" model "Fusion" engine :piece_of_crap end
You could implement the same code by replacing Object#instance_eval with calls to Proc#bind as follows:
def car(&block)
# self is "main" (an Object) here
c = Car.new
# self refers to Car instance when Proc is evaluated on the line below
block.bind(c).call if block_given?
c
end
Remember, Procs are also closures. That is, they take the context within which they were created along with them. For example:
def make_incrementer
x = 0
Proc.new { puts self; x += 1}
end
inc = make_incrementer # Will print 1 to 5 interleaved with "main" 5.times { puts inc.call }
… gives us …
# >> main
# >> 1
# >> main
# >> 2
# >> main
# >> 3
# >> main
# >> 4
# >> main
# >> 5
Wherever you pass that Proc, when you call it, puts self would always say main (which is just the “root” Object of the Ruby interpreter).
So now we add the following code:
class Foo
end
foo = Foo.new rebound_inc = inc.bind(foo)
5.times { puts rebound_inc.call }
We didn’t change the Proc. We just created a Method, rebound_inc, where self now points to our instance of Foo_. So our code, with the above additions, when executed, now returns:
# >> main
# >> 1
# >> main
# >> 2
# >> main
# >> 3
# >> main
# >> 4
# >> main
# >> 5
# >> #<Foo:0x10019092>
# >> 6
# >> #<Foo:0x10019092>
# >> 7
# >> #<Foo:0x10019092>
# >> 8
# >> #<Foo:0x10019092>
# >> 9
# >> #<Foo:0x10019092>
# >> 10
In the example above, reboundinc_ is still a closure over x, defined in make_incrementer, but it’s self now points to whatever object we like.
Posted by evan on Thursday, November 05, 2009
blog comments powered by Disqus
My name is Evan Light and, yes, I am a nerd. I'm also a professional software developer who, after spending one too many years contracting to the federal government, escaped into the far more enjoyable commercial world. Having spent several years using C and even more using Java (the latter very nearly caused me to give up programming entirely), I consider myself fortunate to have discovered Ruby and to use it as part of my daily work.