【问题标题】:Using Refinements Hierarchically分层使用细化
【发布时间】:2015-03-27 22:24:37
【问题描述】:

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"]]

如果:

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 以用于我的方法?

【问题讨论】:

    标签: ruby refinements


    【解决方案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
    

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

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

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

    【讨论】:

    • 太好了!一个细节:您可以考虑将!self.zip(other).map {|x, y| x == y}.include? false 替换为zip(other).all? {|x, y| x == y}。 (回想一下self 是默认接收者。)
    • 啊,是的,谢谢——我养成了一个坏习惯,在任何可能的地方都使用self。这将帮助我记住考虑使用它是否有意义。在没有self 和使用all? 的情况下,它看起来确实更好/更具可读性。
    • 许多 Ruby 爱好者在不需要 self 时使用它,因为他们认为省略它可能会使读者感到困惑。我不在那个阵营,但我不能说他们错了。
    • 对我来说,这似乎真的取决于情况。有时我会故意使用 self 可以省略它,但在这种情况下,代码行似乎很简单,如果省略 self 会更容易阅读。如果它是更复杂/不寻常的代码行,则可能更容易立即了解是否使用了self
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-27
    • 1970-01-01
    • 1970-01-01
    • 2017-04-02
    • 2011-01-29
    • 1970-01-01
    相关资源
    最近更新 更多