7

这里有一个很好的关于当前在 ruby​​ 中实现改进的文档: http ://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html ,但是有一些奇怪的极端情况。

首先,include module正交于using module(一个包括模块的实例方法,而另一个激活细化)。但是包含一个细化模块本身有一个技巧,请参阅 Better way to turn a ruby​​ class into a module而不是使用细化?.

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)

因此,仅使用精化模块的封闭模块。其次,我想使用上面的 yield 技巧来传递一个 Proc 来优化(即使它只接受一个块),而不是像 https://www.new-bamboo.co那样将 Proc 转换回源代码.uk/blog/2014/02/05/refinements-under-the-knife/。但是yield在包含示例中使用 as 不起作用:

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”是细化模块,因此module_eval将“default_definee”置于self=“细化模块”不应影响它。实际上,在开头的“包含”示例中,当我使用module_eval或直接使用yield.

谁能解释这种行为?

4

1 回答 1

4

上下文(或绑定)是 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。结果很像我们最初hello在里面定义的简单class Foo

现在让我们来看看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似乎有该hello方法。这是在顶层yield定义为方法的副作用。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类中运行。

于 2015-07-10T05:12:40.373 回答