可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
So, I am just beginning to learn Ruby and I included a to_s method in my Class so that I can simply pass the Object to a puts method and have it return more than just the Object ID. I made a mistake and defined it as such:
def to_s
puts "I'm #{@name} with a health of #{@health}."
end
instead of:
def to_s
"I'm #{@name} with a health of #{@health}."
end
So, when I do this while using the first code block:
player1 = Player.new("larry")
puts player1
I get an object ID and a string when I execute the above two lines of code and not just the string. Why is this? I get this output:
I'm Larry with a health of 90.
#<Player:0x007fca1c08b270>
I am trying to think about why the first version of the program doesn't just print out the string to console, but instead returns the object ID and the string. I thought that when I pass the object to puts, all that is happening is that puts turns around and calls the to_s method to get the player's string representation. Right?
回答1:
When given arguments that are not strings or arrays puts
calls rb_obj_as_string
to turn its arguments into strings (see rb_io_puts)
If you search for rb_obj_as_string through the ruby codebase (I find http://rxr.whitequark.org useful for this) you can see it's defined as
VALUE rb_obj_as_string(VALUE obj)
{
VALUE str;
if (RB_TYPE_P(obj, T_STRING)) {
return obj;
}
str = rb_funcall(obj, id_to_s, 0);
if (!RB_TYPE_P(str, T_STRING))
return rb_any_to_s(obj);
if (OBJ_TAINTED(obj)) OBJ_TAINT(str);
return str;
}
In brief this:
- returns straightaway if the argument is already a string
- calls
to_s
- if the result is not a string, call
rb_any_to_s
and return that.
rb_any_to_s
is what implements the default "class name and id" result that you're seeing: for any object it returns a string of the form #<ClassName: 0x1234567890abcdef>
Returning to your code, when you run puts player1
it calls rb_obj_as_string
to convert your player to a string.
This first calls your to_s
method, which uses puts
to output your message. Your method then returns nil (because that's what puts
always returns) so ruby calls rb_any_to_s
, and that is what the outermost puts
ends up using.
回答2:
That's because the puts
returns nil, so does that version of to_s
:
def to_s
puts "I'm #{@name} with a health of #{@health}."
end
With puts player1
, player1.to_s
method is called, which prints the String "I'm ...", but the return value is that of the puts
call inside to_s
, which is nil
.
So player1
is an object of which to_s
returns nil, thus puts player1
in the end prints the result of the inherited to_s
method.
回答3:
Experiential Rule: If the result of to_s is not a String, then ruby returns the default.
Application of Rule: puts() returns nil, which means your to_s method returns nil, and nil is not a String, so ruby returns the default.
Another example:
class Object
def inspect
'obj-inspect'
end
def to_s
'obj-to_s'
end
end
class Dog
def inspect
'dog-inspect'
end
def to_s
nil
end
end
puts Dog.new
--output:--
#<Dog:0x1001b6218>
Once to_s fails to return a String, ruby does not continue along the method lookup path to call another to_s method. That makes some sense: the method was found, so there is no need to look up the method in a parent class. Nor does ruby alternatively call inspect() to get a result.
Where does the default come from? I think ruby must directly call the Object#to_s method which is a method written in C--thereby bypassing ruby's method overriding mechanism.
回答4:
The first example using puts
will write to stdout and return nil. It does not actually return a String.
The second example returns a String.
If you want to write to the console you can, but you will need to also return the value.
#or put it in a variable first and return that after you print it
def to_s
puts "I'm #{@name} with a health of #{@health}."
"I'm #{@name} with a health of #{@health}."
end