可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Following is the code I tried to run from the Ruby Programming
Book
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
Why doesn't the product
method give the right output?
I ran it with irb test.rb
. And I am running 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]]
回答1:
By the way: in Ruby 2.0, there are two features which help you with both your problems.
Module#prepend
prepends a mixin to the inheritance chain, so that methods defined in the mixin override methods defined in the module/class it is being mixed into.
Refinements allow lexically scoped monkeypatching.
Here they are in action (you can get a current build of YARV 2.0 via RVM or ruby-build easily):
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:
Since that code example was written, Array
has gained a #product
method and you're seeing the output of that particular method. Rename your module's method to something like product_new
.
回答3:
Add this line at the end of your code :
p Array.ancestors
and you get (in Ruby 1.9.3) :
[Array, Inject, Enumerable, Object, Kernel, BasicObject]
Array is a subclass of Object and has a superclass pointer to Object. As Enumerable is mixed in (included) by Array, the superclass pointer of Array points to Enumerable, and from there to Object. When you include Inject, the superclass pointer of Array points to Inject, and from there to Enumerable. When you write
[1, 2, 3, 4, 5].product
the method search mechanism starts at the instance object [1, 2, 3, 4, 5], goes to its class Array, and finds product (new in 1.9) there. If you run the same code in Ruby 1.8, the method search mechanism starts at the instance object [1, 2, 3, 4, 5], goes to its class Array, does not find product, goes up the superclass chain, and finds product in Inject, and you get the result 120 as expected.
You find a good explanation of Modules and Mixins with graphic pictures in the Pickaxe http://pragprog.com/book/ruby3/programming-ruby-1-9
I knew I had seen that some are asking for a prepend
method to include a module before, between the instance and its class, so that the search mechanism finds included methods before the ones of the class. I made a seach in SO with "[ruby]prepend module instead of include" and found among others this :
Why does including this module not override a dynamically-generated method?
回答4:
In response to @zeronone "How can we avoid such namespace clashes?"
Avoid monkeypatching core classes wherever possible is the first rule. A better way to do this (IMO) would be to subclass 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 may be an OO language, but because it is so dynamic sometimes (I find) subclassing gets forgotten as a useful way to do things, and hence there is an over reliance on the basic data structures of Array, Hash and String, which then leads to far too much re-opening of these classes.
回答5:
The following code is not very elaborated. Just to show you that today you already have means, like the hooks called by Ruby when certain events occur, to check which method (from the including class or the included module) will be used/not used.
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
Rest as in your post. Execution :
$ 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