Project

General

Profile

Actions

Feature #19931

open

to_int is not for implicit conversion?

Feature #19931: to_int is not for implicit conversion?

Added by Dan0042 (Daniel DeLorme) almost 2 years ago. Updated almost 2 years 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) almost 2 years ago Actions #1 [ruby-core:115073]

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) almost 2 years ago Actions #2 [ruby-core:115075]

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) almost 2 years ago Actions #3 [ruby-core:115077]

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) almost 2 years ago Actions #4 [ruby-core:115085]

  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) almost 2 years ago Actions #5 [ruby-core:115088]

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) almost 2 years ago Actions #6 [ruby-core:115089]

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) almost 2 years ago Actions #7 [ruby-core:115090]

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) almost 2 years ago Actions #8 [ruby-core:115093]

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) almost 2 years ago Actions #9 [ruby-core:115094]

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) almost 2 years ago Actions #10 [ruby-core:115100]

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) almost 2 years ago Actions #11 [ruby-core:115429]

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.

Updated by nobu (Nobuyoshi Nakada) almost 2 years ago Actions #12

  • Tracker changed from Misc to Feature

Updated by mame (Yusuke Endoh) almost 2 years ago Actions #13 [ruby-core:115497]

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

Updated by Dan0042 (Daniel DeLorme) almost 2 years ago Actions #14 [ruby-core:115523]

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) almost 2 years ago Actions #15 [ruby-core:115531]

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) almost 2 years ago Actions #16 [ruby-core:115532]

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: PDF Atom

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0