Last active
August 29, 2015 14:02
-
-
Save cheeyeo/9cfc9e8e40dc91d6fdca to your computer and use it in GitHub Desktop.
Conversion Ratio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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