分层使用优化

Using Refinements Hierarchically

Refinements 是对 v2.0 的实验性添加,然后在 v2.1 中进行了修改并永久保留。它提供了一种通过提供 "a way to extend a class locally".

来避免 "monkey-patching" 的方法

我试图将 Refinements 应用于 this recent question,我将这样简化:

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

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

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

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 以便在我的方法中使用?

哇,玩起来真的很有趣!感谢您提出这个问题!我找到了一个可行的方法!

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 的实现细节不够熟悉,无法完全确定为什么会出现这种行为。我的猜测是,refine 块的内部被视为进入不同的顶级范围,类似于在当前文件外部定义的 refines 仅在使用 require 解析定义它们的文件时才适用在当前文件中。这也可以解释为什么嵌套优化不起作用;内部精炼在它退出的那一刻就超出了范围。这将 解释为什么猴子修补 Array 如下工作:

class Array
  using M

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

这不会成为 refine 造成的范围界定问题的牺牲品,因此 String 上的 refine 仍在范围内。