Jonah
Jonah

Reputation: 16202

Dynamically created "method_missing" containing a closure and supporting a passed block

I would like to dynamically create method_missing on an object, while still allowing it to accept blocks. At the same time, I need to use a closure in the dynamically created method_missing. Here's the problem:

  1. To create the closure, I need to use define_singleton_method to create method_missing, passing it a block to create the closure.
  2. Blocks cannot themselves accept blocks, so the newly created method_missing can't accept a block.

Simple demo, for clarification only (ie, I care about solving the general case, not this specific problem):

close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |*all_args|
  meth, *args = all_args
  "The args passed to #{meth}: #{args}. Special code: #{close_over_me}"
end

p o.hi(42) #=> "The args passed to hi: [42]. Special code: 99"

Now let's say I wanted to pass in a block, which changed the special code somehow, eg, by doubling it. That is, I'd like to write:

p o.hi(42) {|x| 2*x}

Keep in mind I want to achieve all of the following:

  1. It will work with any method name.
  2. method_missing must create a closure when it's dynamically defined. Hence cannot do def o.method_missing...
  3. It must work with any number of arguments, and we don't know the number of arguments in advance. The arguments could be of any type.

Is there any metaprogramming magic that can achieve all these at once?

Upvotes: 0

Views: 124

Answers (3)

Wand Maker
Wand Maker

Reputation: 18762

Here is modified code - you can expect a block along with all_args. Block presence has to be validated before issuing a call to it.

Also note that since close_over_me is a string, so doubling it by multiplying by 2 results in "9999".

close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |*all_args, &block|
  meth, *args = all_args
  "The args passed to #{meth}: #{args}. Special code: #{block ? block.call(close_over_me) : close_over_me}"
end

p o.hi(42) {|x| x * 2}
#=> "The args passed to hi: [42]. Special code: 9999"
p o.hi(42)
#=> "The args passed to hi: [42]. Special code: 99"

Upvotes: 0

Jörg W Mittag
Jörg W Mittag

Reputation: 369428

With the define_method family of methods, the parameter list of the block passed to define_method becomes the parameter list of the method defined by define_method, so all you need to do to take a block parameter is to add a block parameter to the block's parameter list:

close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |meth, *args, &blk|
  blk ||= -> x { x }
  "The args passed to #{meth}: #{args}. Special code: #{blk.(close_over_me)}"
end

p o.hi(42) {|x| x*2 }  #=> "The args passed to hi: [42]. Special code: 9999"

Upvotes: 2

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

Though it is definitely possible:

close_over_me = "99"
o = Object.new
o.define_singleton_method(:method_missing) do |*all_args, &cb|
  meth, *args = all_args
  yielded = cb.call(13) if cb
  "Args #{meth}: #{args}. Code: #{close_over_me}. Yielded: #{yielded}."
end

p o.hi(42)
#=> "The args passed to hi: [42]. Special code: 99. Block yielded:."
p o.hi(42) { |x| 2 * x }
#=> "The args passed to hi: [42]. Special code: 99, Block yielded: 26."

I can not get how it is supposed to work with any amount of arguments, since inside the singleton we should call it explicitly.

It seems to me you are misdesigning things.

Upvotes: 0

Related Questions