Project

General

Profile

Actions

Feature #19931

open

to_int is not for implicit conversion?

Added by Dan0042 (Daniel DeLorme) about 1 year ago. Updated 12 months ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:115071]

Description

While reviewing some implicit vs explicit conversion concepts, I discovered that arithmetic operations do not perform the implicit conversion I expected from #to_int

o = Object.new
def o.to_int; 1; end
1 + o  #TypeError

I understand there's the whole #coerce thing for numbers, but I had expected #to_int to fit neatly into this and cause the object to be implicitly coerced to Integer.

So basically I thought that #to_i was for explicit conversion and #to_int for implicit conversion; is that not the case?
Most of the internet seems to think that (to_int : to_i) relationship is like (to_str : to_s). But I can't seems to find authoritative documentation on the topic.

Updated by byroot (Jean Boussier) about 1 year ago

I may be wrong, but my understanding of it is that it's so the right hand side isn't always casted to the left-hand side type.

If to_int was invoked when doing Integer + something, then 1 + 1.5 would be 1 + (1.5).to_int and would return 2 instead of 2.5.

Hence why numerics use coerce instead.

Updated by sawa (Tsuyoshi Sawada) about 1 year ago

Your description suggests a contrast between to_int and to_i, but while you showed a code example using to_int, you have not shown anything using to_i. Furthermore, actually replacing to_int in your code with to_i does not seem to make any difference. Your point is entirely not clear.

Updated by nobu (Nobuyoshi Nakada) about 1 year ago

The order of such calculations is now:

  1. known classes (e.g., Integer#+ knows about Float)
  2. coerce on the RHS.

Otherwise raise an error.

Might consider adding another step there.
3. try the implicit conversion.

Updated by matheusrich (Matheus Richard) about 1 year ago

  1. try the implicit conversion.

If I understood this correctly, that would allow doing things like

1 + "1" # => 2

I hope that's not a direction we wanna follow.

Updated by Dan0042 (Daniel DeLorme) about 1 year ago

sawa (Tsuyoshi Sawada) wrote in #note-2:

Your description suggests a contrast between to_int and to_i, but while you showed a code example using to_int, you have not shown anything using to_i.

If using to_i for explicit conversion, the example would be def o.to_i; 1; end; 1 + o.to_i #=> 2

nobu (Nobuyoshi Nakada) wrote in #note-3:

Might consider adding another step there.
3. try the implicit conversion.

Yes, that's what I was talking about, and why it could work even for 1 + 1.5. Any idea why this isn't done currently? Is it just that no one thought of it?

matheusrich (Matheus Richard) wrote in #note-4:

If I understood this correctly, that would allow doing things like

1 + "1" # => 2

No, because "1" doesn't respond to #to_int

Updated by byroot (Jean Boussier) about 1 year ago

To be fair, + does it for to_str:

class Bar                              
  def to_str                           
    "bar"                              
  end                                  
end                                    
                                       
p "foo" + Bar.new # => "foobar"

So it would seem logical it would do the same for integers as well. I have no idea how much code it would break though.

Updated by Eregon (Benoit Daloze) about 1 year ago

I think the coercion semantics are already quite complex, so we would need a convincing real-world use-case to make this more complicated.

Updated by Dan0042 (Daniel DeLorme) about 1 year ago

byroot (Jean Boussier) wrote in #note-6:

I have no idea how much code it would break though.

It can't break code. It can only cause broken code (as in my example) to suddenly work.

Eregon (Benoit Daloze) wrote in #note-7:

I think the coercion semantics are already quite complex, so we would need a convincing real-world use-case to make this more complicated.

I think it would make things simpler. For 1 + obj you'd only have to define #to_int instead of the much more complex #coerce. And it would smooth out the hard-to-explain discrepancy that to_str/to_ary/to_hash allow implicit conversion but somehow to_int doesn't.

But that's only if the current behavior of to_int wasn't deliberate. And if it was, I'd just like to know why it's this way.

Updated by zverok (Victor Shepelev) about 1 year ago

So basically I thought that #to_i was for explicit conversion and #to_int for implicit conversion; is that not the case?

As far as I understand, it is exactly the case for operations that definitely expect an integer:

o = Object.new
def o.to_int = 5

('a'..'z').to_a[o]
#=> "f"
('a'..'z').first(o)
# => ["a", "b", "c", "d", "e"] 
'a' * o
# => "aaaaa" 

(All of those cases would also clearly state "no implicit conversion of Object into Integer" if the argument wouldn't have #to_int.)

...while number arithmetics seems to be fully defined by #coerce and never tries to perform "implicit conversions".

Updated by Dan0042 (Daniel DeLorme) about 1 year ago

Aha! Thank you, now it makes a lot more sense. While I still think that 1 + o would be nice to have, at least I can see the rhyme and reason.

Updated by matz (Yukihiro Matsumoto) 12 months ago

I agree with the idea of adding to_int to implicit type conversion as @nobu (Nobuyoshi Nakada) stated in #note-3.
Let's experiment.

Matz.

Actions #12

Updated by nobu (Nobuyoshi Nakada) 12 months ago

  • Tracker changed from Misc to Feature

Updated by mame (Yusuke Endoh) 12 months ago

@matz (Yukihiro Matsumoto) Nobu found 0 ^ 1.1 #=> 1 by this change. Is it ok?

Updated by Dan0042 (Daniel DeLorme) 12 months ago

A few weeks ago I experimented and tried to polyfill this via Object#coerce, and it doesn't result in 0 ^ 1.1 == 1

class Object
  def coerce(other)
    if self.respond_to?(:to_int)
      to_int.coerce(other)
    else
      raise TypeError, "#{self.class} can't be coerced into #{other.class}"
    end
  end
end

x = Object.new
def x.to_int = 5

1 + x #=> 6

0 ^ 1.1  #1.1 can't be coerced into Integer (TypeError)

Updated by Eregon (Benoit Daloze) 12 months ago

I think it would be a mistake to try to_int for arithmetic operators.
Why not try to_f, to_r, etc then? (or their implicit conversion variants, although they don't currently exist)
It might accidentally round values which seems pretty bad.
That's the point of coerce, it actually finds a meaningful numeric type for the result.

Updated by Dan0042 (Daniel DeLorme) 12 months ago

Why not try to_f, to_r, etc then? (or their implicit conversion variants, although they don't currently exist)

Because they're for explicit conversion. That would be like implicitly calling #to_i, and that's obviously a bad idea.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0