Ruby:模块,Mixins和Block令人困惑?

以下是我试图从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 2.0中,有两个function可以帮助您解决问题。

Module#prepend mixin添加到inheritance链之前,因此mixin中定义的方法会覆盖正在混合的模块/类中定义的方法。

细化允许词法范围的monkeypatching。

在这里他们正在行动(您可以通过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 

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

在代码末尾添加以下行:

 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] .product

方法搜索机制从实例对象[1,2,3,4,5]开始,转到它的类Array,并在那里找到产品(1.9中的新产品)。 如果你在Ruby 1.8中运行相同的代码,方法搜索机制从实例对象[1,2,3,4,5]开始,转到它的类Array,找不到产品,上升超类链,并找到Inject中的产品,您可以按预期获得结果120。

您可以在Pickaxe中找到带有图形图片的模块和混合的很好的解释http://pragprog.com/book/ruby3/programming-ruby-1-9

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

为什么包含此模块不会覆盖动态生成的方法?

回应@zeronone“我们怎样才能避免这种命名空间冲突?”

尽可能避免monkeypatching核心类是第一条规则。 更好的方法(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的基本数据结构,然后导致这些课程重新开放太多。

以下代码不是很详细。 只是为了向您展示,今天您已经拥有了一些方法,比如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 

在你的post中rest。 执行:

 $ 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