【问题标题】:Ruby: Module, Mixins and Blocks confusing?Ruby:模块、Mixins 和 Blocks 令人困惑?
【发布时间】:2012-12-14 20:10:24
【问题描述】:

以下是我尝试从Ruby Programming Book 运行的代码 http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html

为什么product 方法没有给出正确的输出? 我用irb test.rb 运行它。我正在运行Ruby 1.9.3p194

module Inject
  def inject(n)
    each do |value|
      n = yield(n, value)
    end
    n
  end

  def sum(initial = 0)
    inject(initial) { |n, value| n + value }
  end

  def product(initial = 1)
    inject(initial) { |n, value| n * value }
  end
end

class Array
  include Inject
end

[1, 2, 3, 4, 5].sum            ## 15
[1, 2, 3, 4, 5].product        ## [[1], [2], [3], [4], [5]]

【问题讨论】:

    标签: ruby module mixins block


    【解决方案1】:

    顺便说一句:在 Ruby 2.0 中,有两个功能可以帮助您解决这两个问题。

    Module#prepend在继承链前添加一个 mixin,以便在 mixin 中定义的方法覆盖在它被混入的模块/类中定义的方法。

    细化允许词法范围的猴子补丁。

    他们正在行动(您可以通过 RVM 或 ruby​​-build 轻松获得 YARV 2.0 的当前版本):

    module Sum
      def sum(initial=0)
        inject(initial, :+)
      end
    end
    
    module ArrayWithSum
      refine Array do
        prepend Sum
      end
    end
    
    class Foo
      using ArrayWithSum
    
      p [1, 2, 3].sum
      # 6
    end
    
    p [1, 2, 3].sum
    # NoMethodError: undefined method `sum' for [1, 2, 3]:Array
    
    using ArrayWithSum
    p [1, 2, 3].sum
    # 6
    

    【讨论】:

      【解决方案2】:

      下面的代码不是很详细。只是为了向您展示,今天您已经有了一些方法,例如在某些事件发生时由 Ruby 调用的钩子,来检查将使用/不使用哪个方法(来自包含类或包含的模块)。

      module Inject
          def self.append_features(p_host) # don't use included, it's too late
              puts "#{self} included into #{p_host}"
              methods_of_this_module = self.instance_methods(false).sort
              print "methods of #{self} : "; p methods_of_this_module
              first_letter = []
              methods_of_this_module.each do |m|
                  first_letter << m[0, 2]
              end
              print 'selection to reduce the display : '; p first_letter
              methods_of_host_class = p_host.instance_methods(true).sort
              subset = methods_of_host_class.select { |m| m if first_letter.include?(m[0, 2]) }
              print "methods of #{p_host} we are interested in: "; p subset
              methods_of_this_module.each do |m|
                  puts "#{self.name}##{m} will not be used" if methods_of_host_class.include? m
              end
      
              super # <-- don't forget it !
          end
      

      像你的帖子一样休息。执行:

      $ ruby -v
      ruby 1.8.6 (2010-09-02 patchlevel 420) [i686-darwin12.2.0]
      $ ruby -w tinject.rb 
      Inject included into Array
      methods of Inject : ["inject", "product", "sum"]
      selection to reduce the display : ["in", "pr", "su"]
      methods of Array we are interested in: ["include?", "index",  
       ..., "inject", "insert", ..., "instance_variables", "private_methods", "protected_methods"]
      Inject#inject will not be used
      $ rvm use 1.9.2
      ...
      $ ruby -v
      ruby 1.9.2p320 (2012-04-20 revision 35421) [x86_64-darwin12.2.0]
      $ ruby -w tinject.rb 
      Inject included into Array
      methods of Inject : [:inject, :product, :sum]
      selection to reduce the display : ["in", "pr", "su"]
      methods of Array we are interested in: [:include?, :index, ..., :inject, :insert, 
      ..., :private_methods, :product, :protected_methods]
      Inject#inject will not be used
      Inject#product will not be used
      

      【讨论】:

        【解决方案3】:

        回应@zeronone“我们如何避免这种命名空间冲突?”

        尽可能避免猴子补丁核心类是第一条规则。更好的方法(IMO)是子类 Array:

        class MyArray < Array
          include Inject 
          # or you could just dispense with the module and define this directly.
        end
        
        
        xs = MyArray.new([1, 2, 3, 4, 5])
        # => [1, 2, 3, 4, 5]
        xs.sum
        # => 15
        xs.product
        # => 120
        [1, 2, 3, 4, 5].product
        # => [[1], [2], [3], [4], [5]]
        

        Ruby 可能是一种 OO 语言,但由于它有时(我发现)动态性如此之大,因此子类化被遗忘为一种有用的做事方式,因此过度依赖 Array、Hash 和 String 的基本数据结构,这会导致这些课程过多地重新开放。

        【讨论】:

          【解决方案4】:

          在代码末尾添加这一行:

          p Array.ancestors
          

          你会得到(在 Ruby 1.9.3 中):

          [Array, Inject, Enumerable, Object, Kernel, BasicObject]
          

          Array 是 Object 的子类,并且有一个指向 Object 的超类指针。由于 Enumerable 被 Array 混入(包含)中,因此 Array 的超类指针指向 Enumerable,并从那里指向 Object。当您包含 Inject 时,Array 的超类指针指向 Inject,并从那里指向 Enumerable。当你写

          [1, 2, 3, 4, 5].产品

          方法搜索机制从实例对象 [1, 2, 3, 4, 5] 开始,进入其类 Array,并在那里找到 product(1.9 中的新功能)。如果你在 Ruby 1.8 中运行相同的代码,方法搜索机制从实例对象 [1, 2, 3, 4, 5] 开始,到它的类 Array,没有找到 product,沿着超类链向上,找到在 Inject 中生成产品,您会得到预期的结果 120。

          您可以在 Pickaxe http://pragprog.com/book/ruby3/programming-ruby-1-9 中找到对 Modules 和 Mixins 的很好解释和图形图片

          我知道我已经看到有些人要求prepend 方法在实例及其类之前包含一个模块,以便搜索机制在类的方法之前找到包含的方法。我在 SO 中使用“[ruby] 前置模块而不是包含”进行了搜索,并在其他中发现了这个:

          Why does including this module not override a dynamically-generated method?

          【讨论】:

            【解决方案5】:

            自从编写该代码示例以来,Array 获得了一个 #product 方法,您将看到该特定方法的输出。将模块的方法重命名为 product_new

            【讨论】:

            • 哇哦!谢谢你。我们如何避免这种命名空间冲突? mixin不会覆盖同名方法吗?
            • 我对这种行为感到非常困扰 - 我也希望模块的方法能够覆盖现有方法,就好像它们被猴子补丁一样。避免 Ruby 中的命名空间冲突最好通过测试驱动的开发来完成——这是领域自带的。 (特别是“编写您期望失败的测试观察它失败”部分 - 然后您就不会踩到现有代码的脚趾。)
            • @cfeduke 你可能对我的回答感兴趣 :)
            • @cfeduke:类中定义的方法会覆盖从其他地方继承的方法。这就是继承在 Ruby 和其他所有发明的 OO 语言中的工作方式。如果不是这样,Array 类如何能够用更高效的版本覆盖从Enumerable 继承的方法?
            • 是的,我忽略了注入使模块成为继承层次结构中的祖先,而不是猴子修补。
            猜你喜欢
            • 1970-01-01
            • 2022-01-21
            • 2022-01-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-16
            • 2018-02-26
            相关资源
            最近更新 更多