Feature #19931
opento_int is not for implicit conversion?
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:
- known classes (e.g.,
Integer#+
knows aboutFloat
) -
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
- 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
andto_i
, but while you showed a code example usingto_int
, you have not shown anything usingto_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.
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.