Ruby改进了微妙之处
这里有一个相当不错的ruby细化实现文档:http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html,但有一些奇怪的角落案例。
首先, include module
与using module
正交(一个包含using module
的实例方法而另一个激活细化)。 但是有一个技巧来包含一个优化模块本身,请参阅更好的方法来将Ruby类转换为模块,而不是使用优化?
def to_module(klass)
Module.new do
#note that we return the refinement module itself here
return refine(klass) {
yield if block_given?
}
end
end
class Base
def foo
"foo"
end
end
class Receiver
include to_module(Base) {
def foo
"refined " + super
end
}
end
Receiver.new.foo #=> "refined foo"
奇怪这个改进模块不能用于using
!
m=to_module(Base) {}
m.class #=> Module
using m
#=>TypeError: wrong argument type Class (expected Module)
因此只使用精化模块的封闭模块。 其次,我想使用上面的产量技巧来通过一个Proc来完善(即使通过它只接受一个块),而不是像https://www.new-bamboo.co那样将Proc转换回源代码。 .UK /博客/ 2014/02/05 /细化-下的刀/。 但是在包含示例中使用yield
不起作用:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
using ref_module1(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo'
我们看到Receiver1仍然使用Bar#foo而不是精炼的方法。 我们可以使用module_eval
代替:
def ref_module2(klass,&b)
Module.new do
refine(klass) {
module_eval(&b)
}
end
end
class Receiver2
using ref_module2(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver2.new.bar #=> "refined foo"
我不明白为什么module_eval
在这里工作,而不是yield
方法。 在细化块内部,'default_definee'是细化模块,因此将'default_definee'放置到self
='细化模块'的module_eval
不应该影响它。 事实上,在开始的'包含'示例中,当我使用module_eval
或直接yield
时,我会得到相同的结果。
谁能解释这种行为?
上下文(或绑定)是module_eval工作的原因,yield不在你的最后一组示例中。 它实际上与细化无关,如下所示。
从module_eval
开始:
class Foo
def run(&block)
self.class.module_eval(&block)
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts foo.hello # => "hello"
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError)
在Foo#run
我们称之为module_eval
上Foo
。 这将上下文( self
)切换为Foo
。 结果就像我们原先在class Foo
定义了简单的hello
。
现在我们来看看yield
:
class Foo
def run
yield
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
yield
只是在其原始上下文中调用该块,在本例中它将是<main>
。 调用该块时,最终结果与通常在顶层定义方法的结果完全相同:
class Foo
def run
yield
end
end
foo = Foo.new
def hello
"hello"
end
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
您可能会注意到foo
似乎在yield
示例中使用了hello
方法。 这是定义hello
作为顶级方法的副作用。 事实证明, <main>
仅仅是Object
一个实例,定义顶级方法实际上只是定义了Object
上的私有方法,几乎所有其他事物最终都继承了它。 您可以通过打开irb并运行以下命令来查看:
self # => main
self.class # => Object
def some_method
end
"string".method(:some_method) # => #<Method: String(Object)#some_method>
现在回到你的例子。
以下是yield
示例中发生的情况:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
# like my yield example, this block is going to
# end up being invoked in its original context
using ref_module1(Base) {
def foo
"I'm defined on Receiver1"
end
}
def bar
# calling foo here will simply call the original
# Base#foo method
Base.new.foo
end
end
# as expected, if we call Receiver1#bar
# we get the original Base#foo method
Receiver1.new.bar # => "foo"
# since the block is executed in its original context
# the method gets defined in Receiver1 -- its original context
Receiver1.new.foo # => "I'm defined on Receiver1"
至于module_eval
,它在你的示例中起作用,因为它会导致块在新模块的上下文中运行,而不是在Receiver1
类中运行。
上一篇: Ruby refinements subtleties
下一篇: Spring @transaction not working as expected in junit in non debug mode