The benefits my suggestion provides are :
1) allows an application specific default (of any type) to be supplied,
reducing code required.
2) allows bad input to be unambigously detected, which (can distinguish
"fds".to_i from "0".to_i)
3) because the result of to_i always evaluates to true, you can't do
num.to_i ? 'valid int' : 'invalid int'
but with my sugestion you could do
num.to_i(false) ? 'valid int' : 'invalid int'
4) would be a miniscule change to the existing optimised C unlike some
monkey patch I could do
5) would avoid performance-sapping exceptions
6) would avoid expensive regular expressions
7) as a default parameter, wouldn't affect existing code.
I could be dense; well, I probably am. No, I'm sure about it.
But
let me give it a go anyhow...
All of the functionality you mention can be had now, it's just that it
wouldn't be as fast. So most of the points are moot. Only 5 & 6
remain. Also, 7 isn't exactly true, since it would require a extra
compare operation in the back-end to see if a default was given and
return that, or else return 0. But that is probably negligible.
Regarding 5 & 6. I benchmarked some code against the default to_i/_f.
Here are the code and the results:
$ cat test.rb && ./test.rb
#!/usr/bin/env ruby
class String
def to_i2(default=0)
Integer(self) rescue default
end
def to_f2(default=0)
Float(self) rescue default
end
def num?
self =~ /^[-+.0-9]+$/
end
def to_i3(default=0)
self.num? ? self.to_i : default
end
def to_f3(default=0)
self.num? ? self.to_f : default
end
end
require 'benchmark'
s1 = "10"
s2 = "10a"
s3 = "1.0"
s4 = "1.0a"
n = 1000000
Benchmark.bm { |x|
x.report("to_i valid ") { n.times { s1.to_i } }
x.report("to_i invalid ") { n.times { s2.to_i } }
x.report("to_f valid ") { n.times { s3.to_f } }
x.report("to_f invalid ") { n.times { s4.to_f } }
x.report("to_i2 valid ") { n.times { s1.to_i2 } }
x.report("to_i2 invalid") { n.times { s2.to_i2 } }
x.report("to_f2 valid ") { n.times { s3.to_f2 } }
x.report("to_f2 invalid") { n.times { s4.to_f2 } }
x.report("to_i3 valid ") { n.times { s1.to_i3 } }
x.report("to_i3 invalid") { n.times { s2.to_i3 } }
x.report("to_f3 valid ") { n.times { s3.to_f3 } }
x.report("to_f3 invalid") { n.times { s4.to_f3 } }
}
user system total real
to_i valid 1.160000 0.110000 1.270000 ( 1.307932)
to_i invalid 1.180000 0.100000 1.280000 ( 1.318455)
to_f valid 1.570000 0.190000 1.760000 ( 1.788322)
to_f invalid 1.980000 0.090000 2.070000 ( 2.105102)
to_i2 valid 2.310000 0.350000 2.660000 ( 2.703812)
to_i2 invalid 39.640000 1.240000 40.880000 ( 42.264511)
to_f2 valid 2.880000 0.310000 3.190000 ( 3.377140)
to_f2 invalid 40.680000 1.100000 41.780000 ( 43.211592)
to_i3 valid 6.470000 0.390000 6.860000 ( 6.975072)
to_i3 invalid 3.400000 0.350000 3.750000 ( 3.959219)
to_f3 valid 7.250000 0.320000 7.570000 ( 7.605764)
to_f3 invalid 3.600000 0.380000 3.980000 ( 4.005525)
As you can see, you were correct about point 5 when it is the
exceptional case; however, regarding point 6, performance is close to
within an order of magnitude of the built-in versions of to_i/_f.
That's not too awful.
If I may make three counter-points against your suggestion:
1.) It is wierd and completely unintuitive for to_i to return anything
*other than integer*! Maybe it's just me, but that would be like
calling to_a and getting back a String. Holy return types Batman, what
gives?
2.) Would a non-zero default really be used enough (or in cases where
the speed of using something like the code I listed above with regexps
is not fast enougg) to warrant inclusion? Do you have any real world
examples that are not just corner-cases?
3.) (Like Ara said...) If you're worried about the performance of
exceptions, how helpful is it to do something like: "10a".to_i(nil) %
2? That's either going to terminate with a NoMethodError, or you'll
have to rescue it (eating just as much cycles).