how do you duck-type something to String, so String believes you?

S

Sam Roberts

I can give something a #to_str, which should be an indication that it is
duck-type compat to a String (as opposed to #to_s, which converts
something to a String).

That's all fine, but that doesn't mean that String will allow itself to
be compared to my class:

irb(main):003:0> class Str; def initialize(s) @s = s; end; def to_str()
@s; end end
=> nil
irb(main):004:0> str = Str.new('hi')
=> #<Str:0x339fbc @s="hi">
irb(main):005:0> 'hi' == str
=> false
irb(main):006:0> 'hi' == str.to_str
=> true


I can add as many methods as I want to String, I can even proxy every
single method in it using #undefined_method (which is as duck-typed as
you can get) but I still don't think a String object will ever "== =>
true" to my class.

Am I right about this?

Thanks,
Sam
 
F

Florian Gross

Sam said:
I can add as many methods as I want to String, I can even proxy every
single method in it using #undefined_method (which is as duck-typed as
you can get) but I still don't think a String object will ever "== =>
true" to my class.

Am I right about this?

You need to actually define to_str() (proxying it is not enough as far
as I can tell) and to implement ==:

"foo" == Class.new { def to_str() end; def ==(other) true end }.new
# => true
 
N

Navindra Umanee

Sam Roberts said:
I can give something a #to_str, which should be an indication that it is
duck-type compat to a String (as opposed to #to_s, which converts
something to a String).

String is also strongly-typed in C as T_STRING, so Ruby duck-typing is
not going to save you here. Ruby is riddled with that sort of thing,
probably for efficiency and implementation reasons.

What you can do is override String.== to take this into consideration.

class String
alias :_equals :==
def ==(o)
_equals(o.to_s)
end
end

Cheers,
Navin.
 
E

ES

I can give something a #to_str, which should be an indication that it is
duck-type compat to a String (as opposed to #to_s, which converts
something to a String).

That's all fine, but that doesn't mean that String will allow itself to
be compared to my class:

irb(main):003:0> class Str; def initialize(s) @s = s; end; def to_str()
@s; end end
=> nil
irb(main):004:0> str = Str.new('hi')
=> #<Str:0x339fbc @s="hi">
irb(main):005:0> 'hi' == str
=> false
irb(main):006:0> 'hi' == str.to_str
=> true


I can add as many methods as I want to String, I can even proxy every
single method in it using #undefined_method (which is as duck-typed as
you can get) but I still don't think a String object will ever "== =>
true" to my class.

Am I right about this?

If your class defines #to_str and #<==>, it should work.
String#== checks for #to_str, if that's OK, transfers to
String#<==> which checks for #to_str, if that's OK, transfers
to OtherClass#<==>. Then your #<==> must take a String and
compare it returning -1, 0 or 1.

Not sure if there's a version requirement; works with 1.8.2,
I believe.
Thanks,
Sam

E
 
N

Navindra Umanee

What you can do is override String.== to take this into consideration.
class String
alias :_equals :==
def ==(o)
_equals(o.to_s)
end
end

ES is right, you don't need to redefine == in String if you do it in
Str. I didn't notice that str1 and str2 were swapped around in the
call to rb_equal.

if (TYPE(str2) != T_STRING) {
if (!rb_respond_to(str2, rb_intern("to_str"))) {
return Qfalse;
}
return rb_equal(str2, str1);
}

Clever. :)

Cheers,
Navin.
 
E

ES

ES is right, you don't need to redefine == in String if you do it in
Str. I didn't notice that str1 and str2 were swapped around in the
call to rb_equal.

if (TYPE(str2) != T_STRING) {
if (!rb_respond_to(str2, rb_intern("to_str"))) {
return Qfalse;
}
return rb_equal(str2, str1);
}

Clever. :)

Possibly a bit too clever. While I like the reversal of roles in this
fashion to facilitate the current implementation, I'm somewhat leery
of the concept.

First of all, an *single* explicit method is used, not the representative
API of the class; for example in this instance, anything that defines
#to_s
could be considered to be valid input; sometimes more matching methods
might be required. It is considerably harder to construct the idiom this
way, so I can see the need for this compromise.

Secondly, the naming is confusing. #to_str? str is not a class and a very
similar name #to_s does something different. A better choice would use the
entire class name (lowercase would be fine) and clearly indicate that
it's
not necessarily being made into another class but used as an instance of
the other class; hence SomeClass#as_string or something similar like my
favourite SomeClass#quack_like_a_string.

I think this is being changed somehow in 2.0, even if it's just better
integration and clearer guidelines for usage.
Cheers,
Navin.

E
 
F

Florian Gross

ES said:
Possibly a bit too clever. While I like the reversal of roles in this
fashion to facilitate the current implementation, I'm somewhat leery
of the concept.

First of all, an *single* explicit method is used, not the representative
API of the class; for example in this instance, anything that defines
#to_s
could be considered to be valid input; sometimes more matching methods
might be required. It is considerably harder to construct the idiom this
way, so I can see the need for this compromise.

I'm not sure I get you there -- if anything that implemented .to_s would
be considered a String "5" == 5 would be true as well...
Secondly, the naming is confusing. #to_str? str is not a class and a very
similar name #to_s does something different. A better choice would use the
entire class name (lowercase would be fine) and clearly indicate that
it's
not necessarily being made into another class but used as an instance of
the other class; hence SomeClass#as_string or something similar like my
favourite SomeClass#quack_like_a_string.

I also think that :as_string would be clearer, but I'm not sure if
having to learn this is worth the hassle of breaking compatibility and
perhaps causing more confusion than good.
 
S

Sam Roberts

Quoting (e-mail address removed), on Mon, Mar 14, 2005 at 04:12:24AM +0900:
...


If your class defines #to_str and #<==>, it should work.
String#== checks for #to_str, if that's OK, transfers to
String#<==> which checks for #to_str, if that's OK, transfers
to OtherClass#<==>. Then your #<==> must take a String and
compare it returning -1, 0 or 1.


Did some playing around with this. What I see is that if you want:

"somestr" == your_obj

your_obj needs #to_str and #== (#<=> won't do it)


"somestr" <=> your_obj

your_obj needs #to_str and #<=>


"somestr".eql?(your_obj)

you can't do do this.


So String will 'invert" == and <=> if the argument has a #to_str, but no
other methods will be inverted.


Do I understand correctly?

Sam

The code I played with:

class Str
def to_str; 'str'; end
# def eql?(s); true; end
# def ==(s); true; end
def eql?(s); true; end
# def <=>(s); 0; end
end

p Str.new == 'a'
p 'a' == Str.new

p Str.new.eql?('a')
p 'a'.eql?(Str.new)

p Str.new <=> 'a'
p 'a' <=> Str.new
 
S

Sam Roberts

Quoting (e-mail address removed), on Tue, Mar 15, 2005 at 10:59:08PM +0900:
I'm not sure I get you there -- if anything that implemented .to_s would
be considered a String "5" == 5 would be true as well...

Just things implementing #to_str are considered a string.
I also think that :as_string would be clearer, but I'm not sure if
having to learn this is worth the hassle of breaking compatibility and
perhaps causing more confusion than good.


#to_str doesn't mean that something is a String. It means its a
"str"ing. String (built-in) and Pathname, Exception, and any other class
#to_str "are" strings.

In other languages, they would all implement "interface String", or
inherit from a pure virtual "String" base class, or something..

I think the naming makes sense, looked at this way, and since the naming
isn't going to change (for backwards compat reasons), lets choose to
look at it this way.

Other examples are #to_int and #to_ary.

I'd like to see the beginnings of a "Ruby Idiom Guide". String right now
is magical in how it inverts comparisons to allow things that have
#to_str to compare against itself. It isn't consistent. Fixnum seems to
do it whether #to_int is defined or not. Whats going on there?


$ irb
class One; def to_int; 1; end; def ==(other); 1 == other; end; end
=> nil
one = One.new
=> #<One:0x1d1584>
one == 1
=> true
1 == one
=> true
2 == one
=> false
one == 2
=> false
class Unity; def to_ary; [1,1]; end; def ==(other); [1,1] == other; end; end
=> nil
unity = Unity.new
=> #<Unity:0x33b1a0>
unity == [1,1]
=> true
[1,1] == unity
=> true
class OneX; def ==(other); 1 == other; end; end
=> nil
class UnityX; def ==(other); [1,1] == other; end; end
=> nil
onex = OneX.new
=> #<OneX:0x32290c>
unityx = UnityX.new
=> #<UnityX:0x31daec>
[1,1] == unityx
=> false
1 == onex
=> true

Huh? It always happens? I'll have to look into this more to figure out
whats going on... then submit docs.


Anyhow, I don't think it is common to implement this. But for
duck-typing to be consistent and pervasive we all have to, don't we?

So, for example, if I make a class that is a representation of a concept
of time that has a range past the unix time_t rollover (which kills
Time), and I want people to be able to use it where the use Time and
never know it's my own version, how I should do this needs to be
documented.

For example, I might decide that #to_tim is the way to indicate this (or
#to_time, if you prefer, don't get stuck on the name).

I think I should then override Time#===, #<=>, ... so that if +other+
isn't of class Time, but has a #to_tim method, it would call +other OP
self+.

Some folks talk about duck-typing like it magically happens, but it
doesn't, really, you have to make it happen. I should get
ProgrammingRuby2, see if this is covered there, I just haven't seen it
at a bookstore, yet. Guess I should just order, but I like to support
physical book stores.

Cheers,
Sam
 
B

Bertram Scharpf

Hi,

Am Samstag, 19. Mär 2005, 02:21:15 +0900 schrieb Sam Roberts:
Quoting (e-mail address removed), on Tue, Mar 15, 2005 at 10:59:08PM +0900:

class OneX; def ==(other); 1 == other; end; end
=> nil
class UnityX; def ==(other); [1,1] == other; end; end
=> nil
onex = OneX.new
=> #<OneX:0x32290c>
unityx = UnityX.new
=> #<UnityX:0x31daec>
[1,1] == unityx
=> false
1 == onex
=> true

Huh? It always happens?

Fixnum and Float will call `other.== self' if they don't
find one of their relatives.

File `numeric.c', Function `num_equal()', line 806.

Bertram
 
S

Sam Roberts

Quoting (e-mail address removed), on Sat, Mar 19, 2005 at 07:58:39AM +0900:
Hi,

Am Samstag, 19. Mär 2005, 02:21:15 +0900 schrieb Sam Roberts:
Quoting (e-mail address removed), on Tue, Mar 15, 2005 at 10:59:08PM +0900:

class OneX; def ==(other); 1 == other; end; end
=> nil
class UnityX; def ==(other); [1,1] == other; end; end
=> nil
onex = OneX.new
=> #<OneX:0x32290c>
unityx = UnityX.new
=> #<UnityX:0x31daec>
[1,1] == unityx
=> false
1 == onex
=> true

Huh? It always happens?

Fixnum and Float will call `other.== self' if they don't
find one of their relatives.
File `numeric.c', Function `num_equal()', line 806.

That's surprising, I expected Numeric to only do this if other#to_int or
other#to_float existed.

If doing it all the time is a good idea, why don't Array and String do
it that way?

Sam
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top