To_str behavior

Hi,
I have two related questions on to_str behavior. AFAIK to_str does
automatic
coercion to string wherever one is required, it is to be provided for
objects that exhibit string like behavior. But this mental model did not
match up with my test -

Consider a simple class

class B
def initialize(str)
@value = str
end
def to_s
@value
end
def to_str
to_s
end
end

irb(main):038:0> x = B.new(“hello”)
=> #<B:0x27f1550 @value=“hello”>

irb(main):042:0> a = “hello”+x
=> “hellohello”

So far so good. Concatenation automatically happend. Then I went on to
write
a test -

class MyTest < Test::Unit::TestCase
def test_equality
x = B.new(“hello”)
assert_equal(“hello”, x)
end
end

Which failed to my surprise.

  1. Failure:
    test_equality(MyTest) [(irb):49]:
    <“hello”> expected but was
    <#<B:0x27549bc @value=“hello”>>.

1 tests, 1 assertions, 1 failures, 0 errors

Trying to experiment further I made my class B a subclass of String just
to
check the test -

class B < String
def initialize(str)
@value = str
end
def to_s
@value
end
def to_str
to_s
end
end

irb(main):023:0* a = B.new(“hello”)
=> “”
irb(main):024:0>
irb(main):025:0*
irb(main):026:0* puts a

=> nil

Which surprised me even more.

Any explanation of these two counter-intuitive behaviors will be greatly
appeciated.

Thanks
Nasir

Nasir K. wrote:

Hi,
Greetings Nasir,

class MyTest < Test::Unit::TestCase
def test_equality
x = B.new(“hello”)
assert_equal(“hello”, x)
end
end

Which failed to my surprise.

  1. Failure:
    test_equality(MyTest) [(irb):49]:
    <“hello”> expected but was
    <#<B:0x27549bc @value=“hello”>>.

1 tests, 1 assertions, 1 failures, 0 errors
Okay, as the results showed, an object “x” of class MyTest does not
equal the
string results of the method .to_str called on the x object.

assert_equal (“hello”, x.to_str)

is what you want :slight_smile:

class B < String
def initialize(str)
@value = str
end
def to_s
@value
end
def to_str
to_s
end
end

irb(main):023:0* a = B.new(“hello”)
=> “”
irb(main):024:0>
irb(main):025:0*
irb(main):026:0* puts a

=> nil

Which surprised me even more.
No surprise here, either. You have added a method, to_str to the String
class,
but when you instantiated the Object you did not make any assignment to
the string itself.

If you say, for example:

b= String.new
puts b

you would get the same result, because this is essentially the same
thing.
In your a class, a.to_str should work, I believe, but in my mind, all
bets are off because you are changing the behavior of the built-in
String class, which is generally “a bad thing” unless you are very
careful.

Cheers,

Howard

On 8/13/07, Nasir K. [email protected] wrote:

Hi,
I have two related questions on to_str behavior. AFAIK to_str does automatic
coercion to string wherever one is required,
not quite always, Nasir.
Sometime it is rather done by to_s, look at this example:
519/20 > irb
irb(main):001:0> x=Object.new
=> #Object:0xb7d71fec
irb(main):002:0> puts x
#Object:0xb7d71fec
=> nil
irb(main):003:0> puts x.to_s
#Object:0xb7d71fec
=> nil
irb(main):004:0> x.extend Module::new{ def to_s; “hi there” end}
=> hi there
irb(main):005:0> puts x
hi there
=> nil

it is to be provided for

  @value

=> “hellohello”
yup in this use case you are correct

Which failed to my surprise.
I feel that Howard has explained that nicely :wink:
check the test -
to_s

Which surprised me even more.
I am not sure if it is Howard who has adressed your surprise here
as a matter of fact puts calls to_s on objects that are not strings,
but when they are already they do nothing, they just print the String,
however, as Howard has said correctly you just added @value to a
String that does not make it have the ‘value’ hello.
Try this variation
irb(main):019:0> class B < String
irb(main):020:1> def initialize s
irb(main):021:2> super s
irb(main):022:2> @value = “value = #{s}”
irb(main):023:2> end
irb(main):024:1> end
=> nil
irb(main):025:0> puts B.new(“hi”)
hi
=> nil

you see you get hi and even if you define to_s that does not change
the behavior of puts:
irb(main):026:0> class B
irb(main):027:1> def to_s
irb(main):028:2> @value
irb(main):029:2> end
irb(main):030:1> end
=> nil
irb(main):031:0> puts B.new(“hi”)
hi
=> nil
irb(main):032:0> puts B.new(“hi”).to_s
value = hi
=> nil

Any explanation of these two counter-intuitive behaviors will be greatly
appeciated.
Hopefully it is less counter-intuitive now, I agree that there might
be some slight inconsistencies between to_str and to_s, but for the
second part, be aware that you extended/subclassed a core class, that
is a tricky thing to do sometimes :wink:
HTH
Robert

Thanks to all the respondents.
The confusion stemmed from some of the inconsistency that is around the
to_str usage within Ruby.

See this below.

class A
def to_str
“hello”
end
def to_s
“bye”
end
end

Note to_s and to_str return different strings.

irb(main):018:0> “xyz”+A.new
=> “xyzhello”

Good, as to_str is called when it is ‘expected’ that arg to + is going
to be
a string.

irb(main):019:0> “hello” == A.new
=> false

This is fine too as David pointed out, one could argue though because
there
already is a equal? but anyway…lets move on.

Then see

irb(main):020:0> A.new.upcase
NoMethodError: undefined method `upcase’ for hello:A
from (irb):20

Again since A.new is not a String a method_missing is called etc.

Bear with me a little more :slight_smile:

irb(main):021:0> “hellooo”.include? A.new
=> true

This is good because the argument is expected to be a string.

Now see this

irb(main):022:0> “a” << A.new
=> “ahello”
OK so now Ruby used the string representation from to_str not to_s
Now is it true that only object of class String are expected to be
appended to a string using << operator, hence the coercion?

Not quite -

irb(main):023:0> “a” << 1
=> “a\001”

While of course -
irb(main):025:0> 1.to_str
NoMethodError: undefined method `to_str’ for 1:Fixnum

But of course -
irb(main):038:0> 1.to_s
=> “1”

So is it the case that when a string is “expected” then a to_str is
tried if
not found then to_s is tried, as can be deduced from the last two
operations
above? But then of course the following would fail -

class B
def to_s
“hello”
end
end

irb(main):052:0* “bye”+B.new
TypeError: can’t convert B into String
from (irb):52:in `+’
from (irb):52
from :0

Now one could rationalize each behavior in isolation but isnt there an
inconsistency?

Thanks
Nasir

Nasir K. wrote:

Hi,
I have two related questions on to_str behavior. AFAIK to_str does
automatic
coercion to string wherever one is required, it is to be provided for
objects that exhibit string like behavior. But this mental model did not
match up with my test -

class StringLike
include Comparable
def initialize(val)
@val = val
end
def <=>(other)
@val <=> other.to_str
end
def to_str
@val
end
end

x = StringLike.new(“hi”)
x == “hi” #Â => true
“hi” == x #Â => true

That’s from one of my older experiments. I don’t remember the reasons.
Feel free to examine why it works with <=> + Comparable.
HTH.

Regards
Stefan

Hi –

On Mon, 13 Aug 2007, Nasir K. wrote:

 @value = str

=> #<B:0x27f1550 @value=“hello”>
assert_equal(“hello”, x)
1 tests, 1 assertions, 1 failures, 0 errors
You’re making a strange assumption, namely that Ruby cares whether or
not your assertion succeeds :slight_smile: All that’s happening is:

“hello” == x

and String#== does not do any kind of string conversion on its
operand. It wouldn’t be appropriate, because there’s no reason to
think you want every == test to pass. That’s different from string
concatenation, where you would never have any reason to do:

“str” + x

unless the operation was expected to work.

#to_str is a very “niche” method. It doesn’t morph objects into
strings; rather, it provides an automatic string representation for
cases where it makes no sense for anything but a string to be
involved.

David

Hi –

On Tue, 14 Aug 2007, Nasir K. wrote:

def to_s
a string.

irb(main):019:0> “hello” == A.new
=> false

This is fine too as David pointed out, one could argue though because there
already is a equal? but anyway…lets move on.

I’m digging around in the source to figure out the == behavior. Here’s
the method:

static VALUE
rb_str_equal(str1, str2)
VALUE str1, str2;
{
if (str1 == str2) return Qtrue;
if (TYPE(str2) != T_STRING) {
if (!rb_respond_to(str2, rb_intern(“to_str”))) {
return Qfalse;
}
return rb_equal(str2, str1);
}
if (RSTRING(str1)->len == RSTRING(str2)->len &&
rb_str_cmp(str1, str2) == 0) {
return Qtrue;
}
return Qfalse;
}

I haven’t quite figured out what the point is of testing to see
whether str2 responds to “to_str”, since even if it does, to_str isn’t
called. But I might just have to keep following the trail…

irb(main):021:0> “hellooo”.include? A.new
=> true

This is good because the argument is expected to be a string.

I believe the to_str thing is always about arguments. The object A.new
is not just going to act like a string; you can’t call String instance
methods on it (like upcase). It’s only going to provide its to_str
representation when it’s an argument, as in include? .

irb(main):023:0> “a” << 1
So is it the case that when a string is “expected” then a to_str is tried if
not found then to_s is tried, as can be deduced from the last two operations
above? But then of course the following would fail -

It’s not exactly that. Have a look at this and you’ll see:

irb(main):004:0> “abc” << 1
=> “abc\001”
irb(main):005:0> “abc” << 100
=> “abcd”
irb(main):006:0> “abc” << 10
=> “abc\n”

This operation doesn’t involve to_s or to_str, so it doesn’t tell you
anything about how they work. Your next example answers the question,
though: to_s does not serve as a fall-back for to_str.

David

Hi –

On Wed, 15 Aug 2007, Steven L. wrote:

    return rb_equal(str2, str1);

whether str2 responds to “to_str”, since even if it does, to_str isn’t
called. But I might just have to keep following the trail…

Note the order of arguments. It means str2#== will be used instead of
String#==.

Interesting. I’m not sure what the point of the to_str test is,
though, since to_str doesn’t actually seem to get called.

David

[email protected] writes:

}

called. But I might just have to keep following the trail…
Note the order of arguments. It means str2#== will be used instead of
String#==.

Steve