Closed averbraeck closed 1 year ago
One problem that can be immediately foreseen is that because of type erasure of generics, some of the constructors cannot be easily created: List<Double>
has the same signature as, e.g., List<Length>
. Similarly, SortedMap<Integer, Double>
for sparse vectors has the same erasure signature as SortedMap<Integer, Speed>
. We have to think of a way to solve this. One way could be to check the first item and assume all other entries are of the same type. Note, however, that a Scalar
extends Number
, just like Double
...
Code generation makes it very easy to create these constructors, so that is for sure not a barrier.
Wouldn't instantiation methods using Double
need a unit, where those using Length
or Speed
would not, circumventing type erasure?
LengthVector.instantiate(SortedMap<Integer, Double>, LengthUnit.SI)
LengthVector.instantiate(SortedMap<Integer, Length>)
As Double
is a final class, this would be safe. Using SortedMap<Integer, Number>
or SortedMap<Integer, ? extends Number>
can lead to errors, e.g.:
SortedMap<Integer, Length> map = ...
LengthVector.instantiate(map, LengthUnit.FOOT)` // Length.si from doubleValue() interpreted as foot
I am making a reference implementation, where I can see quickly what works for the constructors and what not.
Combinations of arguments we could include and that make sense; example for an Area object:
DoubleVectorData, AreaUnit
- construct directly from a DoubleVectorData
object with a displayUnit
double[], AreaUnit, StorageType
- construct from double array with a displayUnit
double[], AreaUnit
- construct from double array with a displayUnit
; assume DENSE
b/c of array
double[], StorageType
- construct from double array with SI unit for display
double[]
- construct from double array with with SI unit for display; assume DENSE
b/c of array
Area[], AreaUnit, StorageType
- construct from an Area
array with a displayUnit
Area[], AreaUnit
- construct from an Area
array with a displayUnit
; assume DENSE
b/c of array
Area[], StorageType
- construct from an Area
array with SI unit for display
Area[]
- construct from an Area
array with with SI unit for display; assume DENSE
b/c of array
List<Double>, AreaUnit, StorageType
- construct from Double list with a displayUnit
List<Double>, AreaUnit
- construct from Double list with a displayUnit
; assume DENSE
b/c of array
List<Double>, StorageType
- construct from Double list with SI unit for display
List<Double>
- construct from Double list with with SI unit for display; assume DENSE
b/c of array
Clashes with List<Area>
List<Area>, AreaUnit, StorageType
- construct from an Area
list with a displayUnit
List<Area>, AreaUnit
- construct from an Area
array with a displayUnit
; assume DENSE
b/c of array
List<Area>, StorageType
- construct from an Area
array with SI unit for display
List<Area>
- construct from an Area
array with with SI unit for display; assume DENSE
b/c of array
Clashes with List<Double>
SortedMap<Integer, Double>, int, AreaUnit, StorageType
- construct from sparse Double map with a displayUnit
SortedMap<Integer, Double>, int, AreaUnit
- construct from Double map with a displayUnit
; assume SPARSE
b/c of map
SortedMap<Integer, Double>, int, StorageType
- construct from sparse Double map with SI unit for display
SortedMap<Integer, Double>, int
- construct from sparse Double map with with SI unit for display; assume SPARSE
b/c of map
Clashes with SortedMap<Integer, Area>
SortedMap<Integer, Area>, int, AreaUnit, StorageType
- construct from sparse Area
map with a displayUnit
SortedMap<Integer, Area>, int, AreaUnit
- construct from Area
map with a displayUnit
; assume SPARSE
b/c of map
SortedMap<Integer, Area>, int, StorageType
- construct from sparse Area
map with SI unit for display
SortedMap<Integer, Area>, int
- construct from sparse Area
map with with SI unit for display; assume SPARSE
b/c of map
Clashes with SortedMap<Integer, Double>
I count 24 constructors that could make sense in total...
The clashes can be solved by using a List<? extends Number>
and SortedMap<Integer, ? extends Number>
., since both Scalar
and Double
extend Number
. The nice thing is that the doubleValue()
of a Scalar
is the SI value that needs to be stored.
This would reduce the number of constructors per Vector
to 16... (which is not a problem because the code is generated).
A disadvantage is that the following snippet of code would probably work:
List<Length> ll = List.of(new Length(3.0, LengthUnit.METER),
new Length(2.0, LengthUnit.CENTIMETER));
AreaVector av = new AreaVector(ll, AreaUnit.SQUARE_FOOT);
or even:
List<? extends Scalar> sl = List.of(new Duration(3.0, DurationUnit.SECOND),
new Length(2.0, LengthUnit.CENTIMETER));
AreaVector av = new AreaVector(sl, AreaUnit.SQUARE_FOOT);
A second disadvantage (or problem to solve) is that the displayUnit
for a List<Number>
or Map<Integer, Number>
indicates the unit in which the values are provided. In case of a List<Scalar>
or Map<Integer, Scalar>
, the internal storage is already in SI units, and the displayUnit
is literally only meant for the display unit itself. This means that the doubleValue()
of the underlying Number
has a different meaning, dependent on whether the List/Map contains numbers or scalars...
Probably the last problem can be solved by a constructor like:
public AreaVector(final List<? extends Number> data, final AreaUnit displayUnit, final StorageType storageType)
{
this(data.size() == 0 ? DoubleVectorData.instantiate(new double[] {}, IdentityScale.SCALE, storageType)
: data.get(0) instanceof Area ? DoubleVectorData.instantiate(data, IdentityScale.SCALE, storageType)
: DoubleVectorData.instantiate(data, displayUnit.getScale(), storageType),
displayUnit);
}
Constructors for Vector and FloatVector created and tested -- 100% test coverage and looking good!
Test Coverage for Vector
classes and FloatVector
classes shows correct working of the classes and their constructors.
The construction of Vector and Matrix instances with units is cumbersome, and not intuitive. Currently, constructing involves code like:
More intuitive would be:
The code could assume that the
StorageType
isDENSE
, since we offer the data as an array. In case we would offer the data as aMap
of indices to values, the code could assume that theStorageType
isSPARSE
. This would create in total 20 constructors for each Vector (and a similar number for each Matrix).