我能否动态定义一个接受块的Ruby方法?

9

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

我想动态定义一个既接受可选参数又接受块的方法。在Ruby 1.9中,这很容易实现,因为传递块到块是被允许的。

不幸的是,在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

yield 替换显式的 block.call 也无法解决这个问题。
升级到 Ruby 1.9 对我来说不是一个选择。这是一个无法解决的问题吗,还是有办法解决?

2个回答

7
这在Ruby 1.8.7上可以运行,但1.8.6无法运行:
class X
  define_method(:foo) do |bar, &baz|
    puts bar
    baz.call if baz
  end
end

使用以下内容进行测试:

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)

提供:

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

如果使用1.8.6版本,会出现syntax error, unexpected tAMPER, expecting '|'的错误提示。

如果您想要可选参数和块,可以尝试以下代码:

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

使用以下进行测试:

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

提供:

default
default
  No arg but block

4
你可以使用class_eval和字符串来代替define_method。与define_method相比,这种方法不那么优雅,并且会失去词法作用域,但通常情况下这并不需要。

我需要传递一个字符串给class_eval吗?我不能传递一个块,以保持作用域吗? - andrewdotnich
2
@andrewdotnich 好吧,那么你又回到了如何动态定义方法的问题。 - Daniel Brockman
1
https://github.com/agrimm/zombie-chaser/blob/master/lib/zombie-chaser/chaser.rb 文件中有一个示例和一个博客文章链接,位于文件的中间部分。 - Andrew Grimm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接