Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
468 views
in Technique[技术] by (71.8m points)

metaprogramming - Ruby: why does puts call to_ary?

I'm learning metaprogramming in Ruby and am just trying out defining missing methods via method_missing and define_method. I'm getting some unexpected behaviour and am wondering if anyone can explain this. Here is my class:

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

Now, this code:

x = X.new

x.some_method
puts
x.some_method
puts
puts x

Produces the output:

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>

What I don't get is the last part: why is Ruby calling to_ary in a call to puts? Why would Ruby try to convert my object into an array just to print it?

I've Googled around and found these related links:

These also talk about method_missing and to_ary gotchas, but not specifically about why puts would call to_ary.

I should also mention that the behaviour does not change when I define a to_s, e.g.

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

The output of "puts x" is then:

method to_ary not found. Defining it.
defined method to_ary
I'm an instance of X
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

puts is a synonym for $stdout.puts. $stdout is an IO class, so look at the documentation for IO.puts:

Writes the given objects to ios as with IO#print. Writes a record separator (typically a newline) after any that do not already end with a newline sequence. If called with an array argument, writes each element on a new line.

This mean that puts method is intended to write several lines of output. Thus it tries to call to_ary method on an object and if to_ary is defined, then prints each element of the returned Array on a new line, else puts calls to_s method.

to_ary internal usage is really not well documented in the Ruby documentation (Matz points this out in his The Ruby Programming Language book).

Methods print and p on the other hand don't call to_ary, only to_s.

Sidenote: Interesting, that to_ary must return real Array object, not an object defining each method or something else:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...