4

I have the following kind of method definition:

method_name = :foo
method_arguments = [:bar, :baz]
method_mandatory_arguments = {:quux => true}
method_body = ->{ quux ? bar + baz : bar - baz }

So I want to get a real method. But define_method has no any possibility to define method arguments dynamically. I know another way to use class_eval but I know than defining methods with class_eval is much slower than define_method. How I can effectively archive this?

I did some benchmarks in rails console:

class Foo; end
n = 100_000

Benchmark.bm do |x|
  x.report('define_method') do
    n.times { |i| Foo.send(:define_method, "method1#{i}", Proc.new { |a, b, c| a + b + c }) }
  end
  x.report('class_eval') do
    n.times { |i| Foo.class_eval %Q{ def method2#{i}(a, b, c); a + b + c; end } }
  end
end

So I've got the following results:

                user       system     total      real
define_method  0.750000   0.040000   0.790000 (  0.782988)
class_eval     9.510000   0.070000   9.580000 (  9.580577)
8
  • 1
    class_eval makes sense to me. Is performance really an issue here? It's hard to imagine a use case where it would be. Commented Apr 7, 2013 at 22:21
  • 1
    You could have your define_method block accept varargs, then interpret the args array based on your argument meta data captured in the closure. Commented Apr 7, 2013 at 22:41
  • @dbenhur, ok but in this case in fact I have one method that implements multiply behaviour so I must implement basic argument behaviour by hand (argument presence, default arguments etc). This is what i trying to avoid. Commented Apr 8, 2013 at 7:11
  • Are you sure class_eval is slower for this sort of stuff? My recollection was that because define_method creates a closure it can be slower (or cause leaks) Commented Apr 8, 2013 at 8:09
  • 1
    Can you show us your implementation using class_eval given that method_body is a lambda with no parameters? Commented Apr 8, 2013 at 8:54

1 Answer 1

3

There is no simple way to implement what you are asking using either class_eval or define_method. Since method_body is a lambda it can only access local variables defined right before it.

method_body = ->{ quux ? bar + baz : bar - baz }
quux = true
method_body.call # undefined local variable or method ‘quux’

quux = true
method_body = ->{ quux ? bar + baz : bar - baz }
method_body.call # undefined local variable or method ‘bar’

I would suggest you to revise your requirements. If your method body should be a lambda, define all arguments for it. Then this is a natural fit for define_method:

method_body = ->(quux, bar = 0, baz = 0){ quux ? bar + baz : bar - baz }
define_method(method_name, &method_body)

If your method body can be a string, eval is the only option:

method_body = "quux ? bar + baz : bar - baz"
eval <<RUBY
def #{method_name} #{arguments}
  #{method_body}
end
RUBY
Sign up to request clarification or add additional context in comments.

3 Comments

From your point of view I want to create a proc (or lambda that doesn't matter in my case) with the set of arguments that I store in a specific form. So I have an array method_arguments = [:bar, :baz] and I want to use it like this: method_body = ->(*method_arguments){ quux ? bar + baz : bar - baz }
This is not possible with procs/lambdas. Can you store your method body as a string? It would help if you give a more specific real-world example.
It seems that the only way is to use class_eval.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.