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"