我可以动态定义一个带块的Ruby方法吗?

时间:2021-03-15 23:19:10

I know that I can dynamically define methods on a class using define_method, and that I specify the parameters this method takes using the arity of the block.

我知道我可以使用define_method在类上动态定义方法,并且我使用块的arity指定此方法所使用的参数。

I want to dynamically define a method that accepts both optional parameters and a block. In Ruby 1.9, this is easy because passing a block to a block is now allowed.

我想动态定义一个接受可选参数和块的方法。在Ruby 1.9中,这很容易,因为现在允许将块传递给块。

Unfortunately, Ruby 1.8 doesn't allow this, so the following won't work:

不幸的是,Ruby 1.8不允许这样做,所以以下内容不起作用:

#Ruby 1.8
class X
  define_method :foo do |bar, &baz|
    puts bar
    baz.call if block_given?
  end
end

x = X.new
x.foo("foo") { puts "called!"} #=> LocalJumpError: no block given

Replacing the explicit block.call with yield doesn't fix the problem either.
Upgrading to Ruby 1.9 is unfortunately not an option for me. Is this an intractable problem, or is there a way around it?

用yield替换显式的block.call也不能解决问题。遗憾的是,升级到Ruby 1.9对我来说不是一个选择。这是一个棘手的问题,还是有办法绕过它?

2 个解决方案

#1


6  

This works with Ruby 1.8.7, but not 1.8.6:

这适用于Ruby 1.8.7,但不适用于1.8.6:

class X
  define_method(:foo) do |bar, &baz|
    puts bar
    baz.call if baz
  end
end

Testing with:

X.new.foo("No block")
X.new.foo("With block") { puts "  In the block!"}
p = proc {puts "  In the proc!"}
X.new.foo("With proc", &p)

gives:

No block
With block
  In the block!
With proc
  In the proc!

(with 1.8.6 it gives syntax error, unexpected tAMPER, expecting '|'.)

(1.8.6会出现语法错误,意外的tAMPER,期待'|'。)

If you want optional arguments as well as block, you could try something like this:

如果你想要可选参数以及阻止,你可以尝试这样的事情:

class X
  define_method(:foo) do |*args, &baz|
    if args[0]
      bar = args[0]
    else
      bar = "default"
    end
    puts bar
    baz.call if baz
  end
end

testing with:

X.new.foo
X.new.foo { puts "  No arg but block"}

gives:

default
default
  No arg but block

#2


4  

What you could do is use class_eval with a string instead of define_method. The downside to this (apart from not being as elegant) is that you lose lexical scoping. But this is often not needed.

你可以做的是使用class_eval和字符串而不是define_method。这种缺点(除了不那么优雅)是你失去了词汇范围。但通常不需要这样做。

#1


6  

This works with Ruby 1.8.7, but not 1.8.6:

这适用于Ruby 1.8.7,但不适用于1.8.6:

class X
  define_method(:foo) do |bar, &baz|
    puts bar
    baz.call if baz
  end
end

Testing with:

X.new.foo("No block")
X.new.foo("With block") { puts "  In the block!"}
p = proc {puts "  In the proc!"}
X.new.foo("With proc", &p)

gives:

No block
With block
  In the block!
With proc
  In the proc!

(with 1.8.6 it gives syntax error, unexpected tAMPER, expecting '|'.)

(1.8.6会出现语法错误,意外的tAMPER,期待'|'。)

If you want optional arguments as well as block, you could try something like this:

如果你想要可选参数以及阻止,你可以尝试这样的事情:

class X
  define_method(:foo) do |*args, &baz|
    if args[0]
      bar = args[0]
    else
      bar = "default"
    end
    puts bar
    baz.call if baz
  end
end

testing with:

X.new.foo
X.new.foo { puts "  No arg but block"}

gives:

default
default
  No arg but block

#2


4  

What you could do is use class_eval with a string instead of define_method. The downside to this (apart from not being as elegant) is that you lose lexical scoping. But this is often not needed.

你可以做的是使用class_eval和字符串而不是define_method。这种缺点(除了不那么优雅)是你失去了词汇范围。但通常不需要这样做。