2

以下是我试图从Ruby Programming书中 运行的代码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]]
4

5 回答 5

4

由于编写了该代码示例,Array因此获得了一个#product方法,您将看到该特定方法的输出。将模块的方法重命名为product_new.

于 2012-12-30T05:26:38.543 回答
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中找到对模块和 Mixins 的很好解释

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

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

于 2012-12-30T11:04:13.140 回答
2

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

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

于 2012-12-30T12:00:56.893 回答
2

顺便说一句:在 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
于 2012-12-30T17:27:12.743 回答
1

下面的代码不是很详细。只是为了向您展示今天您已经有方法,例如当某些事件发生时由 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
于 2012-12-30T20:44:25.540 回答