In "What does it mean to use the name of a class for string interpolation?", Candide suggested that #{}
inside a string implicitly calls to_s
.
So, for instance:
my_array = [1, 2, 3, 4]
p my_array.to_s # => "[1, 2, 3, 4]"
p "#{my_array}" # => "[1, 2, 3, 4]"
However, if to_s
for Array is redefined as shown below, I would get different results:
class Array
def to_s
self.map { |elem| elem.to_s }
end
end
p my_array.to_s # => ["1", "2", "3", "4"]
p "#{my_array}" # => "#<Array:0x007f74924c2bc0>"
I suppose this happens any time and anyhow to_s
is overridden.
What am I supposed to do to keep the equality between to_s
and the expression #{}
in a string, if possible?
I came across this issue in a RubyMonk lesson: what according to the lesson #{ogres}
should return, according to my experience is something different.
Please take a look at the documentation for Object#to_s
. It says that to_s
should return a String
. When you override a method, you should always honor its contract. Take a look at the documentatio for Array#to_s
As you can see, it also returns a String
. [Note that this is true for all the to_X
and the to_XYZ
methods: they must always return an object of the corresponding class and they must not raise
an Exception
or otherwise fail.]
Your implementation of to_s
, however, does not return a String
. It returns an Array
, thus violating to_s
's contract. Once you violate a method's contract, all bets are off. Personally, I think it would be more appropriate to raise
a TypeError
exception here, but Ruby is trying to be nice and returns some String
instead, which (in this case) prints the class name and some unique identifier.
Here is the commit to the RubySpec project which (implicitly) states that no Exception
is raise
d and explicitly states that an implementation-defined but otherwise unspecified String
is returned: The spec for interpolation when Object#to_s
did not return a String
was confusing the default representation of an arbitrary object and Object#inspect
.
The latest version of the spec, before the project was closed looks like this language/string_spec.rb#L197-L208
:
it "uses an internal representation when #to_s doesn't return a String" do obj = mock('to_s') obj.stub!(:to_s).and_return(42) # See rubyspec commit 787c132d by yugui. There is value in # ensuring that this behavior works. So rather than removing # this spec completely, the only thing that can be asserted # is that if you interpolate an object that fails to return # a String, you will still get a String and not raise an # exception. "#{obj}".should be_an_instance_of(String) end
As you can see, all that is guaranteed in this case, is that you won't get an Exception
, and that you will get a String
, however, it says nothing about what that String
looks like.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With