Passing a block with define_method

Hi list,

I’ve got a bit of a problem whereby I need to pass a block to a method
defined using define_method this works:

def some_method(*args, &block)

end

but this does not – the interpreter complains about unexpected
ampersands:

define_method(:some_method) do |*args, &block|

end

Is there any way to make this work? I’m doing something a little meta,
so I
don’t know the name of the method or the number of arguments, so it’s
all
got to be dynamic.

On Aug 26, 2008, at 8:30 AM, James C. wrote:

ampersands:

module_eval a string.

a @ http://codeforpeople.com/

module_eval a string.

I’m actually wrapping existing methods so I need to use define_method to
retain access to the old method. Here’s my (broken) code so far:

module Continuations
def accepts_continuation(*args)
args.each do |method_name|
func = instance_method(method_name)
define_method(method_name) do |*params, &block|
value = func.bind(self).call(*params)
end
end
end
end

Hi,

In message “Re: Passing a block with define_method”
on Tue, 26 Aug 2008 23:30:21 +0900, “James C.”
[email protected] writes:

|but this does not – the interpreter complains about unexpected ampersands:
|
|define_method(:some_method) do |*args, &block|
| # …
|end

You have to wait until 1.9.

          matz.

I’m actually wrapping existing methods so I need to use define_method to
retain access to the old method. Here’s my (broken) code so far:

I think I saw this “trick” in one of Ara’s posts :smiley:

  • Store the method somewhere so it is not GC by ruby
  • Retrieve the method object from its object_id with ObjectSpace._id2ref
  • use eval instead of define_method

module Continuations
NOGC = []
def accepts_continuation(*args)
args.each do |method_name|
func = instance_method(method_name)
NOGC << func # Stop garbage collection of the method.
func_object_id = func.object_id
#define_method(method_name) do |*params, &block|
# value = func.bind(self).call(*params)
#end
eval(<<-CODE, FILE, LINE)
def #{ method_name }(*params, &block)
func = ObjectSpace._id2ref(#{ func_object_id })
func.bind(self).call(*params, &block)
end
CODE
end
end
end

end
end
end

To clarify, this formulation does not work:

module Continuations
def accepts_continuation(*args)
args.each do |method_name|
func = instance_method(method_name)
module_eval <<-EOS
def #{method_name}(*params, &block)
value = func.bind(self).call(params)
end
EOS
end
end
end

To clarify, this formulation does not work:

module Continuations
def accepts_continuation(*args)
args.each do |method_name|
func = instance_method(method_name)
module_eval <<-EOS
def #{method_name}(*params, &block)

      value = func.bind(self).call(params)
        where does the "func" method come from here? because you are 

using module_eval, this is just calling a method named “func” on the
target class (the class where you included the module). I don’t think
this would do what you want, even if you do have a “func” method lying
around…

    end
  EOS
end

end
end

Hi –

On Wed, 27 Aug 2008, David A. Black wrote:

The ->(){} thing lets you create a lambda with method-argument
semantics.

Actually the fix is in: you can now (and I think permanently) do this
in 1.9 without the ->(){}.

class C; define_method(:hi) {|*a,&block| block.call if block; puts
a }; end
=> #<Proc:0x1d5754@(irb):1 (lambda)>

C.new.hi(10) { puts “In block” }
In block
10

I’d lost track of where this stood – I’m very glad to see that it’s
there in the block syntax (thanks to Eric M.'s patch, if I’m not
mistaken).

David

On Aug 26, 2008, at 9:12 AM, Emmanuel O. wrote:

I think I saw this “trick” in one of Ara’s posts :smiley:

  • Store the method somewhere so it is not GC by ruby
  • Retrieve the method object from its object_id with
    ObjectSpace._id2ref
  • use eval instead of define_method

that’s correct. you just need to store the data/block somewhere whcih
won’t be GC’d and pull it back using the id

a @ http://codeforpeople.com/

On Aug 26, 2008, at 9:03 AM, James C. wrote:

args.each do |method_name|
func = instance_method(method_name)
define_method(method_name) do |*params, &block|
value = func.bind(self).call(*params)
end
end
end
end

off the top of my head (un-tested but similar will work)

module Continuations

Funcs = {}

def Continuations.funcs() Funcs end

def accepts_continuation *argv

 argv.each do |arg|

   arg = arg.to_s

   Funcs[arg] = instance_method(arg)

   module_eval <<-code
     def #{ arg }(*a, &b)
       Contiuations.funcs.bind(self).call(*a, &b
     end
   code
 end

end
end

a @ http://codeforpeople.com/

On Aug 26, 2008, at 8:30 AM, James C. wrote:

ampersands:


James C.

a working example:

cfp:~ > cat a.rb
module Continuations
Funcs = {}
def Continuations.funcs() Funcs end

def accepts_continuation *argv
argv.flatten.compact.each do |arg|
arg = arg.to_s
klass = Class === self ? self : self.class
Funcs[arg] = klass.send(:instance_method, arg)
klass.module_eval <<-code
def #{ arg }(*a, &b)
Continuations.funcs[#{ arg.inspect }].bind(self).call(*a, &b)
ensure
STDERR.puts ‘wrapped!’
end
code
end
end
end

def foo &b
b.call ‘bar’
end

foo{|bar| puts “foo#{ bar }”}

include Continuations
accepts_continuation :foo

foo{|bar| puts “foo#{ bar }”}

cfp:~ > ruby a.rb
foobar
foobar
wrapped!

a @ http://codeforpeople.com/

Hi –

On Wed, 27 Aug 2008, James C. wrote:

 func = instance_method(method_name)
 define_method(method_name) do |*params, &block|
   value = func.bind(self).call(*params)
 end

end
end
end

In Ruby 1.9 you can do this:

func = ->(*a,&block) { block.call if block; puts a }
=> #<Proc:0x388bdc@(irb):1 (lambda)>

C = Class.new { define_method(:hi, &func) }
=> C

C.new.hi(10) { puts “In block” }
In block
10

The ->(){} thing lets you create a lambda with method-argument
semantics.

David

Just did something similar to Emmanuel’s suggestion, works a treat:

module Continuations
METHODS = {}

def accepts_continuation(*args)
args.each do |method_name|
func = instance_method(method_name)
id = func.object_id
METHODS[id] = func

  module_eval <<-EOS
    def #{method_name}(*params, &block)
      value = Continuations::METHODS[#{id}].bind(self).call(*params)
      block.call(value) if block
      value
    end
  EOS
end

end
end

here’s another way to do it:

m = instance_method(method_name)

define_method "__call__#{method_name}" do |b, *a|
  begin
    m.bind(self).call(*a, &b)
  ensure
    p :WRAPPED!
  end
end

eval <<-EOF
  def #{method_name} *a, &b
    __call__#{method_name}(b, *a)
  end
EOF
  • steve