Disclaimer: The length of this post in particular has nothing whatsoever to do 
with how strong or well-informed I am about the topic. I actually didn't know 
about coercion until today, so I may be missing something obvious. Proceed 
with caution...


On Wednesday, January 26, 2011 10:52:25 pm Tony Arcieri wrote:
> On Wed, Jan 26, 2011 at 9:20 PM, David Masover <ninja / slaphack.com> wrote:
> > Or at least:
> > 
> > if other.kind_of? TimeInterval
> > 
> >  ...
> > 
> > elsif other.kind_of? Numeric
> > 
> >  ...
> > 
> > Remember duck typing?
> 
> Good point,

Well, to a point. I just reflexively change is_a? to kind_of? everywhere I see 
it, unless there's a good reason not to. I don't have a problem with your 
cases, just pointing out an alternative.

> but that said, it can be done even better:
> 
> First, in TimeInterval, alias_method :to_int, :value

If this article is correct:

http://www.rubyfleebie.com/to_i-vs-to_int/

...then I'm not sure that's the case. Do we always want to represent a time 
interval as an integer number of seconds? Maybe, but my instinct is that maybe 
there are other integers we could get out of this object.

> if other.respond_to? :to_int
>   TimeInterval.new(value + other.to_int)
> elsif other.respond_to? :coerce
>   a, b = other.coerce(self)
>   a + b
> else raise TypeError, "#{other.class} can't be coerced into TimeInterval"
> end

While I agree that to_int is the correct way to check if something's an 
integer, the original check was for anything numeric, so it might not be 
sufficient (though it's probably fine for this class so far).

So maybe something like:

if other.respond_to?(:to_int) || other.kind_of?(Numeric)
  TimeInterval.new(value + other.to_int)
elsif other.respond_to? :coerce
  begin
    a, b = other.coerce(self)
    a + b
  rescue TypeError
    if other.respond_to?(:to_i)
      self + other.to_i
    else
      raise
    end
  end
elsif other.respond_to?(:to_i)
  self + other.to_i
else
  raise TypeError, "#{other.class} can't be coerced into TimeInterval"
end

That could probably be DRYed up a bit.

The idea is, if something has a to_int or is a Numeric of some sort, it's 
claiming that it is _just_ a number, so we're probably free to treat it as 
such. Otherwise, let it try to coerce first before we fall back to tricks like 
to_i.

Why?

It's a contrived example to begin with, so my answer will be just as 
contrived, but... Suppose I create, say, an HourInterval class, which 
represents time intervals of hours. Then:

HourInterval.new(5)

You'd expect that to be five hours, right? Then you'd also expect:

HourInterval.new(5).to_i

...that should return 5. However, if we just let it to_i (or to_int, if it's 
pretending to be just a number), then TimeInterval will treat it as five 
_seconds_ instead of five hours, no matter what we do. That's why I'd suggest 
giving it a chance to coerce first, and saving the fuzziest conversions for 
when that fails.

Now the only trick is to make sure that we never call 'coerce' from inside a 
'coerce' method, unless I'm missing something.

I'm told exception handling is expensive. I wonder if it might make sense to 
have a non-raising form of 'coerce', or a can_coerce? method, to avoid that 
overhead. But then, if you're doing this sort of thing and you start caring 
about performance, you can always manually convert things ahead of time.