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
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
.
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?
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.
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
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