On Tue, 2004-10-05 at 11:40, James Edward Gray II wrote:
> On Oct 5, 2004, at 1:20 PM, Edgardo Hames wrote:
> 

> There was an excellent example along these lines in the RubyConf '04 
> blogs.  According to Francis Hwang's blog:
> 
> "Also, Rich [Kilmer] described a cool little hack: Adding time-unit 
> methods to Fixnum so you can write code like time_to_restart = 5.hours 
> + 15.minutes. Dude, I'm totally stealing that."
> 
> I have to agree with the entry, it's a killer trick.
> 

    Here's something I've used from time to time (not completely fleshed
out, since I've just been implementing what I need each time I use it). 
It lets you write things like:

1.0[kilometers]

1.0[kilometers][miles]

1.0[seconds][hours]

1.0[miles/hours]

1.0[kilometers/second][miles/hour]

1.0[kilometers] + 1.0[miles]
 
...and does all the right stuff for you.  The n[kilometers][miles] form,
for example, gives you the distance in miles equal to n kilometers.

-- Markus

P.S. RubyLicence, GPL, your choice, yada yada


require '/usr/local/ruby/1.8/extensions.rb'
#
#  By Markus (MQR) Roberts, 2003+/-1
#
class A_component_vector < Hash
    #
    # Because the keys are dimensions that themselves contain component
    #     vectors, we don't want to deep copy past here.
    #
    def deep_copy
        clone
        end
    end

$known_dimensions = {}
class A_dimension
    attr_accessor :name
    def initialize(name)
        @name = name
        end
    def components
        A_component_vector.new(self=>1.0)
        end
    def *(other)
       
A_derived_dimension.new("#{self.name}*#{other.name}",self.components+other.components)
        end
    def /(other)
       
A_derived_dimension.new("#{self.name}/#{other.name}",self.components-other.components)
        end
    def to_str
        @name
        end
    end

class A_derived_dimension < A_dimension
   attr_reader :components,:hash
   def initialize(name,components)
       super(name)
       @components = components
       #@hash = 0
       #@components.each_pair { |k,v| @hash += v*k.hash }
       @hash = @components.inject(0) { |x,p| x+p[1]*p[0].hash }
       end
   def ==(other)
       self.class == other.class and
         @hash == other.hash and
         @components.length == other.components.length and
         @components.all? { |k,v|
             v == other.components[k]
             }
       end
   end

def define_dimension(name,components=nil)
    name = name.to_s.intern
    $known_dimensions[name] = components ?
A_derived_dimension.new(name,components.components) :
A_dimension.new(name)
    #eval "#{name.to_s.capitalize} = $known_dimensions[#{name.inspect}]"
    eval %Q{
        def #{name}
            $known_dimensions[#{name.inspect}]
            end
        }
    end

define_dimension :duration
define_dimension :distance
define_dimension :area,     distance*distance
define_dimension :speed,    distance/duration
#define_dimension :velocity, displacement/duration

$known_units = {}
class A_unit
   attr_accessor :what,:how_much,:name
   def initialize
       end
   def A_unit.to_measure(what,name,conversion=nil)
       result = A_unit.new
       result.name = name
       conversion = 1.0 unless conversion
       conversion = conversion.value*conversion.units.how_much if
conversion.is? A_measure
       $known_units[what] ||= {}
       $known_units[what][result] = conversion
       result.what     = what
       result.how_much = conversion
       result
       end
   def *(other)
      
A_unit.to_measure(self.what*other.what,"(#{self.name})*(#{other.name})",self.how_much*other.how_much)
       end
   def /(other)
      
A_unit.to_measure(self.what/other.what,"(#{self.name})/(#{other.name})",self.how_much/other.how_much)
       end
   def to(units)
       return 1.0 if units == self
       fail "Can't coerce #{self.what.name} to #{units.what.name}" if
self.what != units.what
       fail "Can't coerce #{self} to #{units}" if self.what !=
units.what
       return self.how_much/units.how_much
       end
   def to_s
       @name #$known_units[@what]
       end
   def to_str
       @name #$known_units[@what]
       end
   end

class A_measure
    attr_reader :value,:units
    def initialize(value,units)
        @value = value
        @units = units
        end
    def [](units)
        p "Trying to convert #{self} to #{units}\n"
        A_measure.new(@units.to(units)*@value,units)
        #@units.to(units)*@value
        end
    def *(other)
        case other
          when A_measure then
A_measure.new(@value*other.value,@units*other.units)
          else                A_measure.new(@value*other,     
@units            )
          end
        end
    def /(other)
        case other
          when A_measure then
A_measure.new(@value/other.value,@units/other.units)
          else                A_measure.new(@value/other,     
@units            )
          end
        end
    def +(other)
        A_measure.new(@value+other[@units].value,@units)
        end
    def -(other)
        A_measure.new(@value-other[@units].value,@units)
        end
    def to_s
        "#{value}[#{units}]"
        end
    def to_str
        "#{value}[#{units}]"
        end
    end
    
class Numeric
    def [](units)
        A_measure.new(self,units)
        end
    end

def define_unit_of(what,names,comparison=nil)
    names = [names,names.to_s+'s'] unless names.is_a? Array
    names.each { |name|
        eval %Q{
            def #{name}(quantity=nil)
                if quantity
                    A_measure.new(quantity,#{name})
                  else
                   
A_unit.to_measure(#{what.name},"#{name}",#{comparison ? comparison :
'nil'})
                  end
                end
            }
        }
    end
    
def define_metric_unit_of(what,name,comparison=nil)
    define_unit_of what,name,comparison
    define_unit_of what,'nano' +name.to_s,1e-9[name]
    define_unit_of what,'micro'+name.to_s,1e-6[name]
    define_unit_of what,'milli'+name.to_s,1e-3[name]
    define_unit_of what,'centi'+name.to_s,1e-2[name]
    define_unit_of what,'deci' +name.to_s,1e-1[name]
    define_unit_of what,'deka' +name.to_s,1e+1[name]
    define_unit_of what,'kilo' +name.to_s,1e+3[name]
    define_unit_of what,'mega' +name.to_s,1e+6[name]
    define_unit_of what,'giga' +name.to_s,1e+9[name]
    end

define_metric_unit_of distance,  :meter
define_unit_of        distance,  [:foot,:feet],   0.3048[meters]
define_unit_of        distance,  :mile,        1609.344[meters]
define_metric_unit_of duration,  :second
define_unit_of        duration,  :minute,        60.0[seconds]
define_unit_of        duration,  :hour,          60.0[minutes]

fail "Type checking doesn't work!" unless begin
    print "#{1.0[kilometers] + 1.0[seconds]}\n"
    false
  rescue
    true
  end

print "#{1.0[kilometers]}\n"
print "#{1.0[kilometers][miles]}\n"
print "#{1.0[seconds][hours]}\n"
print "#{1.0[miles/hours]}\n"
print "#{1.0[kilometers/second][miles/hour]}\n"
print "#{1.0[kilometers] + 1.0[miles]}\n"