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:
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.
Sam Roberts wrote: > 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 ==:
Sam Roberts <srobe...@uniserve.com> wrote: > 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
>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:
>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.
> 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); }
>> 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.
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.
ES wrote: > 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.
Quoting ruby...@magical-cat.org, on Mon, Mar 14, 2005 at 04:12:24AM +0900:
> On 3/13/2005, "Sam Roberts" <srobe...@uniserve.com> wrote:
> >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:
..
> >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.
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
Quoting f...@ccan.de, on Tue, Mar 15, 2005 at 10:59:08PM +0900:
> ES wrote:
> >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...
Just things implementing #to_str are considered a string.
> >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.
#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.