Skip to content

Instantly share code, notes, and snippets.

@cheeyeo
Last active August 29, 2015 14:02
Show Gist options
  • Save cheeyeo/9cfc9e8e40dc91d6fdca to your computer and use it in GitHub Desktop.
Save cheeyeo/9cfc9e8e40dc91d6fdca to your computer and use it in GitHub Desktop.
Conversion Ratio
class Quantity
include Comparable
CoercedNumber = Struct.new(:value) do
def +(other) raise TypeError; end
def -(other) raise TypeError; end
def *(other)
other * value
end
def /(other) raise TypeError; end
end
attr_reader :magnitude
alias_method :to_f, :magnitude
private :magnitude
def initialize(magnitude)
@magnitude = magnitude.to_f
freeze
end
def to_s
"#{@magnitude} #{self.class.name.downcase}"
end
def to_i
to_f.to_i
end
def inspect
"#<#{self.class}:#{@magnitude}>"
end
def +(other)
other = ensure_compatible(other)
self.class.new(@magnitude + other.to_f)
end
def -(other)
raise TypeError unless other.is_a?(self.class)
self.class.new(@magnitude - other.to_f)
end
def *(other)
multiplicand = case other
when Numeric then other
when self.class then other.to_f
else
raise TypeError, "Don't know how to multiply by #{other}"
end
self.class.new(@magnitude * multiplicand)
end
def /(other)
raise TypeError unless other.is_a?(self.class)
self.class.new(@magnitude / other.to_f)
end
def <=>(other)
raise TypeError unless other.is_a?(self.class)
other.is_a?(self.class) && magnitude <=> other.to_f
end
def hash
[magnitude, self.class].hash
end
def coerce(other)
unless other.is_a?(Numeric)
raise TypeError, "Can't coerce #{other}"
end
[CoercedNumber.new(other), self]
end
def ensure_compatible(other)
if other.is_a?(self.class)
return other
elsif other.respond_to?(:convert_to)
other.convert_to(self.class)
else
fail TypeError
end
end
def convert_to(target_type)
ratio = ConversionRatio.find(self.class, target_type) or
fail TypeError, "Cannot convert #{self.class} to #{target_type}"
target_type.new(magnitude * ratio.number)
end
alias_method :eql?, :==
end
ConversionRatio = Struct.new(:from,:to,:number) do
def self.registry
@registry ||= []
end
def self.find(from, to)
registry.detect{|ratio| ratio.from == from && ratio.to == to}
end
end
class Feet < Quantity
end
class Meters < Quantity
end
def Feet(value)
case value
when Feet then value
else
value = Float(value)
Feet.new(value)
end
end
def Meters(value)
case value
when Meters then value
else
value = Float(value)
Meters.new(value)
end
end
ConversionRatio.registry << ConversionRatio.new(Feet, Meters, 0.3048) <<
ConversionRatio.new(Meters, Feet, 3.28084)
meters = Feet(32).convert_to(Meters) # => #<Meters:9.7536>
meters.convert_to(Feet) # => #<Feet:32.000001024>
Feet(32) + Meters(23) # => #<Feet:107.45932>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment