2

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]]
zeronone
  • 2,912
  • 25
  • 28

5 Answers5

4

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.

cfeduke
  • 23,100
  • 10
  • 61
  • 65
  • Oh wow! Thank you. How can we avoid such namespace clashes? Doesn't the mixin overwrite methods with the same names? – zeronone Dec 30 '12 at 05:40
  • 3
    I'm greatly bothered by this behavior - I too expect the module's methods to overwrite the existing methods as though they were monkeypatched. Avoiding namespace clashes in Ruby is best done by test driven development - that comes with the territory. (Specifically the "write the test you expect to fail *and* watch it fail" part - then you don't step on existing code's toes.) – cfeduke Dec 30 '12 at 06:00
  • @cfeduke You may be interested by my answer :) – BernardK Dec 30 '12 at 11:08
  • @cfeduke: Methods defined in a class override those inherited from elsewhere. That's just how inheritance works both in Ruby and in every other OO language ever invented. How would the `Array` class be able to override the methods inherited from `Enumerable` with more efficient versions if that weren't the case? – Jörg W Mittag Dec 30 '12 at 14:27
  • Yeah I had overlooked that inject makes the module an ancestor in the inheritance hierarchy, not monkey patched. – cfeduke Dec 30 '12 at 14:39
4

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?

Community
  • 1
  • 1
BernardK
  • 3,674
  • 2
  • 15
  • 10
2

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.

ian
  • 12,003
  • 9
  • 51
  • 107
2

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
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
1

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
BernardK
  • 3,674
  • 2
  • 15
  • 10