Ruby: модуль, миксины и блоки запутывают?
Ниже приведен код, который я пытался запустить из Ruby Programming
Book
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]]
Ответы
Ответ 1
Кстати: в Ruby 2.0 есть две функции, которые помогут вам справиться с вашими проблемами.
Module#prepend
добавляет mixin к цепочке наследования, поэтому методы, определенные в методах переопределения mixin, определенных в модуле/классе, смешиваются.
Уточнения позволяют лексически охватить обезьянную палочку.
Здесь они находятся в действии (вы можете получить текущую сборку YARV 2.0 через RVM или ruby-build легко):
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
Поскольку этот пример кода был написан, Array
получил метод #product
, и вы видите вывод этого конкретного метода, Переименуйте свой модульный метод на что-то вроде product_new
.
Ответ 3
Добавьте эту строку в конец вашего кода:
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].product
механизм поиска метода начинается с объекта экземпляра [1, 2, 3, 4, 5], переходит в его класс Array и находит продукт (новый в 1.9). Если вы запустили тот же код в Ruby 1.8, механизм поиска метода начинается с объекта экземпляра [1, 2, 3, 4, 5], переходит в его класс Array, не находит продукт, не поднимается над цепочкой суперкласса и не находит продукт в Inject, и вы получите результат 120, как ожидалось.
Вы найдете хорошее объяснение модулей и микшинов с графическими изображениями в Pickaxe http://pragprog.com/book/ruby3/programming-ruby-1-9
Я знал, что видел, что некоторые из них просят использовать метод prepend
для включения модуля до, между экземпляром и его классом, чтобы механизм поиска обнаруживал включенные методы перед классами. Я сделал soach в SO с "[ruby] prepend module вместо include" и нашел среди прочего следующее:
Почему включение этого модуля не отменяет динамически сгенерированный метод?
Ответ 4
В ответ на @zeronone "Как избежать таких конфликтов пространства имен?"
Избегайте, если это возможно, первое правило для основных классов ядра. Лучший способ сделать это (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, что приводит к слишком большому повторному открытию этих классов.
Ответ 5
Следующий код не очень проработан. Просто чтобы показать вам, что сегодня у вас уже есть средства, такие как крючки, вызываемые Ruby при возникновении определенных событий, для проверки того, какой метод (из включенного класса или включенного модуля) будет использоваться/не использоваться.
module Inject
def self.append_features(p_host) # don't use included, it 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