squants
squants copied to clipboard
Why Price[Energy] is not a Quantity?
Hi,
I was going to code a TimeSeries class for the Energy domain enforcing that values in the timeseries must be of type [A <: Quantity[A]]. So far so good but then I tried to create a TimeSeries of Price[Energy] and it happens that a Ratio is not a quantity. Digging deeper I see other "Ratio" quantities like Velocity having its own non-derived types.
Is this something by design or something that could be improved in current implementation? Should I create an EnergyPrice type like the one in Velocity? Is it something maybe related to https://github.com/garyKeorkunian/squants/issues/127 ?
Regards.
Hi,
It's true Price is not treated as a quantity but a ratio between Money (a quantity) and other Quantities); and that other ratios are are treated as quantities.
My thinking around these semtantics was that Price is not an "amount" of something. That is, it's not really a quantity. I can have an amount of money, and I can have an amount of energy, but I can't have an amount of price. Other ratios do meet this criteria: I can have an amount of energy, an amount of time and an amount of power (the ratio of the former 2).
Of course, the real question is why are you trying to use a unified type. If you are simply looking to ensure everything in the timeseries is the same type, a simple TimeSeries[A] should solve the problem.
If you need to perform a polymorphic operation on the underlying members of the timeseries - and therefore looking for a method that works on both classes - the problem would be better solved with a type class. You may already be familiar with that concept, but here's a primer:
// define an interface for the operations you want to perform on time series members
trait TimeSeriesMemberOps[A] {
def someOp(a: A, ...)
}
// Ensure your time series class requires a parameter type with an available type class
class TimeSeries[A]( ... )(implicit val tsops: TimeSeriesMembersOps) {
def someMethod() = tsops.someOp( // pass in a member of series )
}
// Define Impl's for each type you want to put in the series
class QuantityTimeSeriesOps extends TimeSeriesMemberOps[Quantity[_]] {
def someOp(a: Quantity[_]) = // define operation for Quantities
}
class PriceTimeSeriesOps extends TimeSeriesMemberOps[Price[_]] {
def someOp(q: Price[_]) = // define opeations for Prices
}
This is the better choice for implementing polymorphic operations on types that are not part of the same type hierarchy.
The reason for the unified type was convenience. When serializing the timeseries to Json we were trying to avoid the per point unit of measurement verbosity and instead serialize Doubles and tag the whole time series from the unit of the Quantity. We've considered type classes but since our need was serialization, we've created specialized serializers, one when the type is a Quantity and several ones when the type isn't.
Besides the philosophical discussion whether a Price is a Quantity or not (for us it is because is the amount of money that flows to our system in that timeseries per unit of Energy), I could imagine other magnitudes that could benefit from the Ratio being a Quantity specially as the result of some computations. For instance some physical constants like gravity, concentrations or the like.