Integer with units
Often we have Integer
’s but don’t have a clue what the unit is unless we encode it in a varible name. How can we prevent adding two numbers which are of different or incompatible units…
class IntegerWithUnit < SimpleDelegator
attr_reader :unit
def initialize(integer, unit)
@unit = unit.to_sym.freeze
super(integer)
end
def unit?(unit)
@unit == unit.to_sym
end
def to_int
self
end
def to_s
super + " " + unit.to_s
end
def to_str
to_s
end
def inspect
to_s
end
end
num = IntegerWithUnit.new(1, :byte)
# => 1 byte
"Remaining: #{num}"
# => "Remaining: 1 byte"
num + 2
# => 3
(num + 2).class
# => Integer
# oops, we need to return an IntegerWithUnit
IntegerWithUnit.new(1, :byte) == IntegerWithUnit.new(1, :mile)
# => true
# oops, no it doesn't...
class Byte < IntegerWithUnit
def initialize(number)
super(number, :byte)
end
end
We can then allow certain types to be involved in arithmetic, e.g.
n1 = Byte.new(1)
n2 = Megabyte.new(2)
n1 + n2
# => 1000001 byte
Do we need to add pluralization, so:
Byte.new(2)
# => 2 bytes
This would need an inflection library, or it is an optional arguments, e.g.:
IntegerWithUnit.new(2, [:byte, :bytes])
What about Floats?
FloatWithUnit.new(2.0, :miles)
# => 2.0 mile
IntegerWithUnit.new(2, :bytes).to_f
#=> 2.0 byte
And sugar:
Byte.new(2).to_megabyte
Mile.new(2).to_kilometers
Where do we store the conversion ratios between units?
In a chosen base unit, e.g. Byte has all conversions to Megabyte, Gigabyte etc. What about going from say kilometers to miles…
Maybe we have to convert to a base unit, do addition, and then back to the target unit. Each class has a conversion to the base unit, so Byte::ToByte = 1
, Kilobyte::ToByte = 1000
etc.
If this where to be gemified then we would want to wrap all class in a module instead of poluting the root namespace. In which case we could have:
module Unit
class Integer < SimpleDelegator
attr_reader :unit
def initialize(integer, unit)
@unit = unit.to_sym.freeze
super(integer)
end
# ...
end
class Byte < Integer
end
end
But then does a Unit::Byte
inherit from Unit::Integer
or Unit::Float
?
Would we need a base class, ValueWithUnit
, which picks the correct class, Integer
versus Float
… Is it even needed since we are delegating to the passed in value…
IntegerWithUnit.new(1.2, :byte)
# => 1.2 byte
IntegerWithUnit.new({}, :byte)
# => {} byte
So what about:
module Unit
class Object < SimpleDelegator
attr_reader :unit
def initialize(obj, unit)
@unit = unit.to_sym.freeze
super(obj)
end
# ...
end
class Byte < Object
def initialize(number)
super(number, :byte)
end
end
end
There are so many different units they might need to be in seperate gems, e.g. bitcoin denominations.
From here it is proberbly best to write a README showing the desired API and then copy the examples to form the basis of the feature specs.