Руби: почему путы вызывают to_ary?

Я изучаю метапрограммирование в Ruby и просто пытаюсь определить отсутствующие методы с помощью method_missing и define_method. Я получаю неожиданное поведение, и мне интересно, может ли кто-нибудь объяснить это. Вот мой класс:

class X
  def method_missing(m, *args, &block)
    puts "method #{m} not found. Defining it."
    self.class.send :define_method, m do
      puts "hi from method #{m}"
    end
    puts "defined method #{m}"
  end  
end

Теперь этот код:

x = X.new

x.some_method
puts
x.some_method
puts
puts x

Производит вывод:

method some_method not found. Defining it.
defined method some_method

hi from method some_method

method to_ary not found. Defining it.
defined method to_ary
#<X:0x007fcbc38e5030>

Чего я не понимаю, так это последней части: почему Ruby вызывает to_ary в вызове puts? Зачем Ruby пытаться преобразовать мой объект в массив только для того, чтобы распечатать его?

Я погуглил и нашел эти связанные ссылки:

В них также говорится о подводных камнях method_missing и to_ary, но не конкретно о том, почему puts вызывает to_ary.

Я также должен упомянуть, что поведение не меняется, когда я определяю to_s, например.

def to_s
  "I'm an instance of X"
end

Выход «помещает x» тогда:

method to_ary not found. Defining it.
defined method to_ary
I'm an instance of X

person Tom De Leu    schedule 22.01.2012    source источник


Ответы (1)


puts является синонимом $stdout.puts. $stdout — это класс IO, поэтому посмотрите документацию для ввод-вывод:

Записывает заданные объекты в ios, как с IO#print. Записывает разделитель записей (обычно новую строку) после всех, которые еще не заканчиваются последовательностью новой строки. При вызове с аргументом массива записывает каждый элемент в новую строку.

Это означает, что метод puts предназначен для записи нескольких строк вывода. Таким образом, он пытается вызвать метод to_ary для объекта, и если to_ary определен, то печатает каждый элемент возвращенного Array в новой строке, иначе puts вызывает метод to_s.

to_ary внутреннее использование на самом деле плохо документировано в документации по Ruby (Мац указывает на это в своей книге Язык программирования Ruby).

С другой стороны, методы print и p не вызывают to_ary, а только to_s.

Примечание: интересно, что to_ary должен возвращать реальный объект Array, а не объект, определяющий метод each или что-то еще:

class Test
  def to_ary
    10.downto(1)
  end
end

puts Test.new

#TypeError: can't convert Test to Array (Test#to_ary gives Enumerator)
#        from (irb):28:in `puts'
#        from (irb):28:in `puts'
#        from (irb):28
person Aliaksei Kliuchnikau    schedule 22.01.2012
comment
Спасибо. Я думаю, что суть в том, что внутреннее использование to_ary действительно плохо документировано в документации Ruby :) Я только что прочитал документы IO.puts, они явно не упоминают to_ary, я думаю, это должно быть яснее. Спасибо, что указали на книгу «Язык программирования Ruby», возможно, вы ее прочтете. - person Tom De Leu; 22.01.2012