Displaying articles with tag inspect

That First Line of Ruby

Posted by wesgibbs, Wed Apr 09 15:37:00 UTC 2008

Probably the very first piece of ruby code most of us wrote was some invocation of the puts method. In those early days, I remember grokking enough to know that puts added a newline and print did not and then I moved on.

Recently, I was watching a coworker give a demonstration to our team of the facade pattern using ruby. His example worked great, except that the output wasn’t formatted the way he expected. He had overridden the to_s method in one of his classes and was using Kernel#p to output the object.

class Grouper
attr_accessor :taste, :color, :price
  def initialize(taste, color, price)
    @taste, @color, @price = taste, color, price
  end
  def to_s
    "Taste: #{taste}, Color: #{color}, Price: #{price}"
  end
end

p Grouper.new("good", "white", "cheap")
which yielded
#<Grouper:0x314494 @taste="good", @price="cheap", @color="white">

He was expecting his overridden to_s method to have been invoked, and it wasn’t.

I decided it might be a good idea to go back and dig a little deeper into some of the ways ruby provides for getting output to $stdout.

To start, let’s take every newbie’s favorite method, puts. So hearkening back to those heady early moments with Ruby, I fired up irb and invoked puts:


>> puts "Oi" 
Oi
=> nil

What just happened? Well, we know that the puts message must have a receiver, even though it’s not apparent from this invocation through irb. To track down what receiver I was sending the puts message to, I started with ruby info:

ri puts

which didn’t get me very far:

IO#puts, IRB::Locale#puts, IRB::Notifier::AbstructNotifier#puts,
IRB::OutputMethod#puts, Kernel#puts, Net::Telnet#puts,
Net::WriteAdapter#puts,
Net::SSH::Service::Process::OpenManager#puts,
Net::SSH::Service::Process::POpen3Manager::SSHStdinPipe#puts,
StringIO#puts, XMP#puts, XMP::StringInputMethod#puts,
Zlib::GzipWriter#puts, TMail::Decoder#puts, TMail::Decoder#puts,
TMail::Encoder#puts, TMail::Encoder#puts

So which one of these puts was being invoked? If you took the time to read the list, there’s really only one likely candidate, but still, let’s try a more direct approach. Back in irb


>> self.class
=> Object

The “hidden” receiver of the puts message is the Object class. If we check Object’s instance methods


>> Object.instance_methods.include?("puts")
=> false

we see that puts isn’t one of them. Let’s check its privates


>> Object.private_methods.include?("puts")
=> true

and there it is. But Object#puts wasn’t one of the results of our ruby info request. To be precise, puts is actually a private instance method declared by the Kernel module. Remember that the Object class doesn’t define any instance methods; it gets them all from mixing in the Kernel module. This misled me for some time because the docs show lots of methods for Object. Then I bothered to read the class documentation for Object, which points out

    
"Although the instance methods of +Object+ are defined by the +Kernel+ module, we have chosen to document them here for clarity." 

Had the opposite effect on me, but oh well. Nonetheless, now we know that the method we’ve been calling from irb is Kernel#puts. Back to ri

------------------------------------------------------------ Kernel#puts
     puts(obj, ...)    => nil
------------------------------------------------------------------------
     Equivalent to

         $stdout.puts(obj, ...)
$stdout is a pre-defined variable referencing an instance of the class IO. So at long last, with one final stroke of ri, we find the method we’ve been invoking all this time
---------------------------------------------------------------- IO#puts
     ios.puts(obj, ...)    => nil
------------------------------------------------------------------------
     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. If called without
     arguments, outputs a single record separator.

And all it does is write out what you pass it, adding the slightest bit of formatting in the form of new lines after each object. (The record separator used is a bit of a mystery.) What if you pass it something other than a string? Then that object’s to_s method is used to get the string representation of the object.

Let’s move on to another popular method, print. As I said at the beginning, I used to just think of print as being like puts but without the new line. That is correct, but is not the only difference. When print is invoked without an explicit receiver in irb, it’s the IO class that receives the print message, just like puts. In the case of print, however, there is some extra logic that gets applied to the formatting. print will add the value of the output field separator variable ($,) after each field, and append the value of the output record separator variable ($\) to the end of the output.

So typically those variables are nil

>> print "one", "two", "three" 
onetwothree=> nil

Nothing between the fields, and nothing at the end of the output. If you give those variables values


>> $\ = " [louise] " 
>> $, = " [mabel] " 
>> print "one", "two", "three" 
one [mabel] two [mabel] three [louise] => nil

To finish, we’ll look at Object#inspect and Kernel#p. The key difference between Object#inspect and the other methods we’re looking at is that Object#inspect does not write anything to $stdout and returns a String. The other methods all write to $stdout and return nil.

The common use case for Object#inspect is seeing what the current state of the object is, i.e. what all its instance variables are set to. By default, inspect returns a string comprising the object’s class and id, and the state of its variables.


>> g = Grouper.new("awful", "white", "cheap")
>> g.inspect
=> "#<Grouper:0x3503f4 @color=\"white\", @taste=\"good\", @price=\"cheap\">" 

Object#inspect does some simple formatting, like surrounding strings with quotes. One thing to note is that Object#inspect does not bother itself with Grouper’s to_s method.

Kernel#p takes one or more objects as arguments and just iterates through them, calling Object#inspect for each, and sending the output to $stdout.

>> p({"one" => 1, "two" => 2, "three" => 3}, "Frank", ["green", "blue"], 5, Object.new)
{"three"=>3, "two"=>2, "one"=>1}
"Frank" 
["green", "blue"]
5
#<Object:0x81790>
=> nil
So back to my coworkers’ example with the Grouper class. He had used the Kernel#p method to print out a string representation of his object. Kernel#p just sends the inspect message to Grouper and writes the returned string out to $stdout. At no point does the to_s message get sent to Grouper. This, however, would have given him what he wanted

>> puts Grouper.new("good", "white", "cheap")
Taste: good, Color: white, Price: cheap
=> nil
Here’s a summary of the four methods we looked at
                       writes to $stdout | invokes obj.to_s | returns
IO#puts(obj, ...)             YES        |        YES       |   nil
IO#print(obj, ...)            YES        |        YES       |   nil
Object#inspect                NO         |        NO        |   String
Kernel#p(obj, ...)            YES        |        NO        |   nil

If these don’t give you the control or formatting you need, then take a look at the PrettyPrint class and its Modules.

0 comments | Filed Under: | Tags: inspect