【问题标题】:Ruby refinement issues with respond_to? and scopingrespond_to 的 Ruby 细化问题?和范围
【发布时间】:2015-11-05 05:15:50
【问题描述】:

我正在尝试将实例方法 foo 添加到 Ruby 的 Array 类 所以当它被调用时,数组的字符串元素会变成字符串“foo”。

这可以通过猴子修补 Ruby 的 StringArray 类轻松完成。

class String
  def foo
    replace "foo"
  end
end

class Array
  def foo
    self.each {|x| x.foo if x.respond_to? :foo }
  end
end

a = ['a', 1, 'b']
a.foo
puts a.join(", ")   # you get 'foo, 1, foo' as expected

现在我正在尝试使用 Ruby 2 的 refinements feature 重写上述内容。 我使用的是 Ruby 版本 2.2.2。

以下工作(在文件中,例如 ruby​​ test.rb,但由于某种原因不在 irb 中)

module M
  refine String do
    def foo
      replace "foo"
    end
  end
end

using M
s = ''
s.foo
puts s      # you get 'foo'

但是,将 foo 添加到 Array 类时,我无法让它工作。

module M
  refine String do
    def foo
      replace "foo"
    end
  end
end

using M

module N
  refine Array do
    def foo
      self.each {|x| x.foo if x.respond_to? :foo }
    end
  end
end

using N

a = ['a', 1, 'b']
a.foo
puts a.join(", ")   # you get 'a, 1, b', not 'foo, 1, foo' as expected

有两个问题:

  1. 使用新方法优化类后,respond_to? 不起作用,即使您可以调用 对象上的方法。尝试添加puts 'yes' if s.respond_to? :foo 作为第二个代码 sn-p 的最后一行,您会看到 not 打印了“yes”。
  2. 在我的数组优化中,String#foo 超出了范围。如果你删除 if x.respond_to? :foo 从 Array#foo,你会得到错误undefined method 'foo' for "a":String (NoMethodError)。所以问题是:如何使 String#foo 细化在 Array#foo 细化中可见?

我如何克服这两个问题才能让它发挥作用?

(请不要提供不涉及细化的替代解决方案,因为这是一个理论练习,因此我可以学习如何使用细化)。

谢谢。

【问题讨论】:

    标签: ruby monkeypatching ruby-2.1 ruby-2.2


    【解决方案1】:
    1. respond_to? 方法不起作用,这已记录在案 here.

    2. 问题是您只能在顶层激活细化 它们在范围上是词法的。

    一种解决方案是:

    module N
      refine String do
        def foo
          replace 'foobar'
        end
      end
    
      refine Array do
        def foo
          self.each do |x|
            x.foo rescue x
          end
        end
      end
    end
    
    using N
    
    a = ['a', 1, 'b']
    p a.foo
    
    puts a.join(", ") # foo, 1, foo
    

    【讨论】:

      【解决方案2】:

      再次以您的示例为例,一个简单的解决方案可能是覆盖 respond_to?细化块中的方法:

      module M
        refine String do
          def foo
            replace "foo"
          end
          def respond_to?(name,all=false)
            list_methods = self.methods.concat [:foo]
            list_methods.include? name
          end
        end
      
        refine Array do
          def foo
            self.each {|x| x.foo if x.respond_to? :foo }
          end
        end
      
      end
      
      using M
      
      a = ['a', 1, 'b']
      a.foo
      puts a.join(", ")    # you get 'foo, 1, foo'
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-13
        • 2011-10-20
        • 1970-01-01
        • 2014-11-10
        • 2018-04-08
        • 1970-01-01
        • 2014-12-28
        • 2011-08-17
        相关资源
        最近更新 更多