Can you enable coercion between types?


#1

I have a ruby class called Duration that represents a unit of time.
It’s main attribute is a number that represents the duration in
seconds.

If I try to save it to a DB using Rails, I get a “Duration can not be
coerced into Float” error. Is there a way to modify the definition of
Duration to enable automatic coercion into a float? (I’m thinking of
something like adding a “to_float” method to the class.)

thanks in advance
larry


#2

On Sun, 19 Mar 2006, larry wrote:

larry
try this:

 harp:~ > cat a.rb
 class Duration
   def initialize seconds
     @seconds = seconds
   end
   def coerce other
     if other.class == @seconds.class
       [@seconds, other]
     else
       [Float(@seconds), Float(other)]
     end
   end
 end

 p(40.0 + Duration::new(2.0))
 p(40 + Duration::new(2))


 harp:~ > ruby a.rb
 42.0
 42

hth.

-a


#3

removed_email_address@domain.invalid wrote:

     else
       [Float(@seconds), Float(other)]
     end
   end
 end

Could you explain what this is doing? I don’t get the effect.

Thanks,
T.


#4

Did you try #to_f?

T.


#5

On Sun, 19 Mar 2006, Trans wrote:

     if other.class == @seconds.class
       [@seconds, other]
     else
       [Float(@seconds), Float(other)]
     end
   end
 end

Could you explain what this is doing? I don’t get the effect.

my understanding of coerce is that it’s used to produce a pair of
operands -
myself and other - which can be used to perform operations.

so, in the case of Fixnum#+ we might see

def + other
x, y = other.coerce self
x + y
end

which is to say Fixnum need not know about how to add every possible
class -
rather it leaves that knowledge up to those classes to export via thier
coerce
method. as i understand it that method should return two new objects
which
interoperate so, in the case above (which is made up) we might have

duration = Duration::new 42.0

42 + duration

here would call duration(42) and expect back an x and y which could be
added
together. so, if @seconds happens to be a Fixnum the result will be
another
Fixnum. if @seconds happens to a Float the result would be a Float.

i admit that i’m not crystal clear on this, but it sure seems to work:

 harp:~ > cat a.rb
 class Duration
   def initialize seconds
     @seconds = seconds
   end
   def coerce other
     if other.class == @seconds.class
       [@seconds, other]
     else
       [Float(@seconds), Float(other)]
     end
   end
   def + other
     x, y = coerce(other)
     x + y
   end
 end
 def Duration(*a, &b); Duration::new(*a, &b); end

 p( 40 + Duration(2.0) )   # Float 42.0
 p( 40 + Duration(2) )     # Fixnum 42
 p( 40.0 + Duration(2.0) ) # Float 42.0
 p( 40.0 + Duration(2) )   # Float 42.0
 p( Duration(40) + 2 )     # Fixnum 42
 p( Duration(40) + 2.0 )   # Float 42.0
 p( Duration(40.0)  + 2)   # Float 42.0
 p( Duration(40.0) + 2.0 ) # Float 42.0


 harp:~ > ruby a.rb
 42.0
 42
 42.0
 42.0
 42
 42.0
 42.0
 42.0

i assume one would do something nice with the coerce method using Time,
Date,
and DateTime objects too…

regards.

-a


#6

On Sat, 2006-03-18 at 17:11, removed_email_address@domain.invalid wrote:

     else
       [Float(@seconds), Float(other)]
     end
   end

There’s a gottcha here. Coerce is used to implement double dispatch and
so needs to return the coerced values in the opposite order (see Pick
Axe)–(other,self), not (self,other).

The way you wrote it works fine for addition & multiplication, but
you’ll pull your hair out the first time you try to subtract or divide.

–MarkusQ

P.S. And I think (as Tom noted) that “to_f” was the answer to the
original question.


#7

On Sun, 19 Mar 2006, Markus wrote:

     if other.class == @seconds.class

The way you wrote it works fine for addition & multiplication, but
you’ll pull your hair out the first time you try to subtract or divide.

indeed. good catch.

cheers.
-a