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"