分层使用细化

时间:2015-03-26 22:48:42

标签: ruby refinements

Refinements是对v2.0的实验性补充,然后在v2.1中进行了修改并永久化。它提供了一种避免"猴子修补的方法。通过提供"一种在本地扩展课程的方法"。

我尝试将Refinements应用于this recent question,因此我将简化:

a = [[1, "a"],
     [2, "b"],
     [3, "c"],
     [4, "d"]]

b = [[1, "AA"],
     [2, "B"],
     [3, "C"],
     [5, "D"]]

i中偏移a处的元素与i中偏移b处的元素匹配,如果:

a[i].first == b[i].first

a[i].last.downcase == b[i].last.downcase

换句话说,字符串的匹配与大小写无关。

问题是要确定与a的相应元素匹配的b元素的数量。我们看到答案是两个,即偏移12的元素。

执行此操作的一种方法是修补String#==

class String
  alias :dbl_eql :==
  def ==(other)
    downcase.dbl_eql(other.downcase)
  end
end

a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } }
  #=> 2

或改为使用Refinements

module M
  refine String do
    alias :dbl_eql :==
    def ==(other)
      downcase.dbl_eql(other.downcase)
    end
  end
end

'a' == 'A'
  #=> false (as expected)
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } }
  #=> 0 (as expected)

using M
'a' == 'A'
  #=> true
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } }
  #=> 2

但是,我想像这样使用Refinements

using M
a.zip(b).count { |ae,be| ae == be }
  #=> 0

但是,如你所见,这给出了错误的答案。这是因为我正在调用Array#==并且细化不适用于Array

我可以这样做:

module N
  refine Array do
    def ==(other)
      zip(other).all? do |ae,be|
        case ae
        when String
          ae.downcase==be.downcase
        else
          ae==be
        end
      end  
    end
  end
end

using N
a.zip(b).count { |ae,be| ae == be }
  #=> 2

但那不是我想要的。我想做这样的事情:

module N
  refine Array do
    using M
  end   
end

using N
a.zip(b).count { |ae,be| ae == be }
  #=> 0

但显然不起作用。

我的问题:有没有办法优化String以便在Array中使用,然后优化Array以便在我的方法中使用?

1 个答案:

答案 0 :(得分:1)

哇,这真是非常有趣的玩!谢谢你提出这个问题!我发现了一种有效的方法!

module M
  refine String do
    alias :dbl_eql :==
      def ==(other)
        downcase.dbl_eql(other.downcase)
      end
  end

  refine Array do
    def ==(other)
      zip(other).all? {|x, y| x == y}
    end
  end
end

a = [[1, "a"],
     [2, "b"],
     [3, "c"],
     [4, "d"]]

b = [[1, "AA"],
     [2, "B"],
     [3, "C"],
     [5, "D"]]

using M

a.zip(b).count { |ae,be| ae == be } # 2

如果不在==中重新定义Array,则优化不会适用。有趣的是,如果你在两个单独的模块中进行操作,它也不起作用;这不起作用,例如:

module M
  refine String do
    alias :dbl_eql :==
      def ==(other)
        downcase.dbl_eql(other.downcase)
      end
  end
end

using M

module N
  refine Array do
    def ==(other)
      zip(other).all? {|x, y| x == y}
    end
  end
end

a = [[1, "a"],
     [2, "b"],
     [3, "c"],
     [4, "d"]]

b = [[1, "AA"],
     [2, "B"],
     [3, "C"],
     [5, "D"]]

using N

a.zip(b).count { |ae,be| ae == be } # 0

我对refine的实现细节不太熟悉,对此行为发生的原因完全有信心。我的猜测是,精炼块的内部被视为进入不同的顶级范围,类似于当前文件之外定义的精炼仅适用于定义它们的文件是用require解析的在当前文件中。这也可以解释为什么嵌套的精炼不起作用;内部精炼在它退出的那一刻就超出了范围。这将解释为什么猴子修补Array如下工作:

class Array
  using M

  def ==(other)
    zip(other).all? {|x, y| x == y}
  end
end

这不会成为refine创建的范围问题的牺牲品,因此refine上的String仍然在范围内。