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

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

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.

···

Sam Roberts <sroberts@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).

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

···

On 3/13/2005, "Sam Roberts" <sroberts@uniserve.com> wrote:

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 ==:

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

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. :slight_smile:

Cheers,
Navin.

Quoting ruby-ml@magical-cat.org, on Mon, Mar 14, 2005 at 04:12:24AM +0900:

>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

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

···

On 3/13/2005, "Sam Roberts" <sroberts@uniserve.com> wrote:

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. :slight_smile:

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

···

On 3/13/2005, "Navindra Umanee" <navindra@cs.mcgill.ca> wrote:

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 flgr@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.

Cheers,
Sam

Hi,

···

Am Samstag, 19. Mär 2005, 02:21:15 +0900 schrieb Sam Roberts:

Quoting flgr@ccan.de, 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

--
Bertram Scharpf
Stuttgart, Deutschland/Germany
http://www.bertram-scharpf.de

Quoting lists@bertram-scharpf.de, on Sat, Mar 19, 2005 at 07:58:39AM +0900:

Hi,

> Quoting flgr@ccan.de, 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

···

Am Samstag, 19. Mär 2005, 02:21:15 +0900 schrieb Sam Roberts: