Forum: Ruby Block Trouble (Binding Issue?)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Pieter V. (Guest)
on 2007-05-19 23:39
(Received via mailing list)
I'm presently trying to work on a little metaprogramming project, but
am finding my efforts a little frustrated by ... well, I'm not rightly
sure what by.  It seems to be a scoping issue, from what I can tell.

Here's a sample case:

############
class BlockTester
  def x
    "Correct Method!"
  end

  def self.x
    "WRONG METHOD!"
  end

  def self.meth(name, &b)
    # ... do some stuff
    define_method name, &b
  end

  def self.normal_meth(name, &b)
    meth(name, &b)
  end

  def self.special_meth(name, &b)
    meth(name) do
      # do something special
      yield # <= This is the problematic call
    end
  end
end

# Test Case

class Foo < BlockTester
  meth "foo" do x end
  normal_meth "bar" do x end
  special_meth "baz" do x end
end

f = Foo.new
f.foo # => "Correct Method!"
f.bar # => "Correct Method!"
f.baz # => "WRONG METHOD!"
############

The goal, as may not be immediately apparent from the code, is for
"special_meth" to be able to create a new method that automatically
executes a few lines of code before executing the block.  While this
is similar in principle to overriding a method in a subclass...

############
class X
  def to_s
    "green" + super
  end
end
############

... I can't seem to make this very common pattern stick for a block.
Any suggestions?
Joel VanderWerf (Guest)
on 2007-05-20 00:37
(Received via mailing list)
Pieter V. wrote:
> I'm presently trying to work on a little metaprogramming project, but
> am finding my efforts a little frustrated by ... well, I'm not rightly
> sure what by.  It seems to be a scoping issue, from what I can tell.

Yup, it sure is. define_method(name, &block) changes the scope of block
to the instance, but the yield still references a block in class Foo
scope.

A fix might be to replace yield with instance_eval(&b), as below, but
that will preclude passing arguments to the block, in case you ever need
to do that. Google for instance_exec for a discussion, hacks for the
present, and hope for the future (1.9 has it)...

>  def self.special_meth(name, &b)
>    meth(name) do
>      # do something special
>      yield # <= This is the problematic call
        instance_eval(&b)
Pieter V. (Guest)
on 2007-05-20 01:02
(Received via mailing list)
Arg.  Thanks.  I would have sworn I tried that at some point.  Works
like a charm.
Robert D. (Guest)
on 2007-05-20 01:24
(Received via mailing list)
On 5/19/07, Joel VanderWerf <removed_email_address@domain.invalid> wrote:
> to do that. Google for instance_exec for a discussion, hacks for the
> present, and hope for the future (1.9 has it)...

As we just had a discussion about it maybe you might be interested in
this, it comes from Mauricio F.' Eigenclass. Hopefully he does
not mind that I post this slightly modified version. The original can
be found here http://eigenclass.org/hiki/bounded+space+instance_exec
there is very good stuff over there.
Especially as you are interested in metaprogramming.
All I added to Mauricio's code is to remove the temporary method
*before* calling it, thus avoiding a theoretical possibility of
endless recursion in the temporary method call.

class Object
 include InstanceExecHelper = Module.new
 def instance_exec(*args, &block)
   begin
     old_critical, Thread.critical = Thread.critical, true
     n = 0
     n += 1 while respond_to?(mname="__instance_exec#{n}")
     InstanceExecHelper.module_eval{ define_method(mname, &block) }
     mthd = method(mname)
     InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
   ensure
     Thread.critical = old_critical
   end
   mthd.call *args
 end
end
.
HTH
Robert
This topic is locked and can not be replied to.