Ruby: Module, Mixins and Blocks confusing?

前端 未结 5 627
忘了有多久
忘了有多久 2021-01-15 12:55

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

相关标签:
5条回答
  • 2021-01-15 13:09

    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.

    0 讨论(0)
  • 2021-01-15 13:20

    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?

    0 讨论(0)
  • 2021-01-15 13:25

    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.

    0 讨论(0)
  • 2021-01-15 13:25

    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
    
    0 讨论(0)
  • 2021-01-15 13:26

    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
    
    0 讨论(0)
提交回复
热议问题