Closed keilw closed 5 years ago
Unit.alternate(String)
changes the textual representation of the Unit
instance itself, by assigning a new symbol. But Unit.mix(Unit)
(in my understanding) does not operate on the Unit
itself, but rather on the textual representation of the Quantity
instances using that Unit
. With the addition of this method, Unit
is no longer an object describing only unit of measurement. That method introduces a new aspect, the textual representation of numbers. My objections are:
Unit
now taking care of textual representation of Quantity
)FOOT.mix(INCH)
? If the number for "2 feet 6 inches" is 2.5, then the units of measurement is FOOT
; the FOOT.mix(INCH)
unit does not means anything for Number
or double
values.Do we agree on the following points?
Unit.mix(Unit)
is trying to solve is to control the textual representation of quantities using that unit.Unit.mix(Unit)
does not have any impact on numbers (not to be confused with textual representation of numbers). For example it changes nothing to the values stored in java.lang.Double
.Unit.mix(Unit)
does not have any impact on the textual representation of the Unit
instance: FOOT.mix(INCH)
is still represented as "foot" if the number stored for "2 feet 6 inches" is 2.5 value. This is different than Unit.alternate(String)
which change the Unit
symbol.If the above is true, then Unit.mix(Unit)
is truly about Quantity
formatting, with nothing about unit of measurement. In such case I suggest that this functionality should be provided in Quantity
or QuantityFormat
interface.
No, that's not the case. As in Wolfram's case as MixedUnit is mainly for arithmetic operations like conversion or others. It allows to create a MixedQuantity, but IMO has absolutely nothing to do with formatting. In UnitFormat the only thing to control would be if you want to show it as "feet" or "ft", not the rest, that belongs in MixedUnit. Unit.mix() operates on the Unit by combining it with others, just like alternate or transfer do.
Can you be more specific? Which of above points 1, 2 or 3 is not the case? And what are the arithmetic with mixed units? For example what is the result of converting 2.5 from FOOT
to FOOT.mix(INCH)
?
That is the subject of https://github.com/unitsofmeasurement/indriya/issues/200, the conversion should be possible in both directions, along the lines of UnitConvert[Quantity[3.2, "Days"], MixedUnit[{"Days", "Hours", "Minutes"}]]
in Wolfram, but let's only answer here for the API, whether or not it makes sense to expose entry points like Unit.mix()
or QuantityFactory.create()
for these.
conversion should be possible in both directions
Okay, but what would be the numerical result of converting 2.5 from FOOT
to FOOT.mix(INCH)
?
That's the goal, but please discuss it in https://github.com/unitsofmeasurement/indriya/issues/200. The result of converting to a MixedUnit
shall be a MixedQuantity
which holds the exact numerical values for each "sub-unit". While getValue()
acts like any other quantity showing the result in the "reference unit" (that is the leading unit, currently the first in the list but in theory it could be another, that's an implementation detail)
I would like to keep API and implementation separated. My concern is "what should be the method contract, regardless of any implementation". Then the details of Indriya implementation is another issue.
Then do a thumb down and let's see what others think. Every method like alternate(), annotate()
or transfer()
are tightly coupled to the RI and match a specific class there. If we really wanted to enforce such a separation we shall deprecate and eventually remove a few of these. prefix()
is different because it uses API elements although it may end up instantiating a particular class, too.
alternate(String)
as contract which is specified in the API Javadoc.annotate()
and transfer()
are currently specific to Indriya (I do not see them in the API). What the implementation choose to do for those methods is not a concern for the API.prefix(Prefix)
method is okay because its behavior is well defined. Note: the javadoc should be expanded however.The behavior of mix(Unit)
is not well defined at this time. "Defined in Indriya" is not a satisfactory answer; we needs to write down the expected behavior first (regarding effects on unit conversions, etc.). Currently the only proposal is a method signature; nothing else. This is not enough for starting a vote!
To repeat my question: what would be the numerical result of converting 2.5 from FOOT
to FOOT.mix(INCH)
? This is an API question, not an implementation details. The question "what would be the result" should be answered here. Question about the details of how the result is computed belong to Indriya.
It's not, because a mixed unit is implemented in the RI. The behavior of mix()
is defined just as well as all the others, the result has to be a Unit
, otherwise if seshat or other implementations chose to deal with mixed units in a single class, that is fine.
This method will require an addition to QuantityFactory
to make sense on the API level.
transform
and alternate
were there so we cannot remove without deprecation.
Do you mean that Unit.mix(Unit)
is an Idriya-specific thing? If so, no problem with that provided that the method stay in Idriya. Either the method stay in Idriya, or either (if the method is declared in Unit
interface) we must define the method contract. What is the situation?
transform
and alternate
has never been questioned in this issue; they are not the topic.
No it's not Indriya specific. A "mixed unit" is just a universal as a transformed, alternate or other derived unit, so they all meet the same kind of needs.
Then if it is not Indriya specific, we need to define its behavior here (and implementation details in Indriya).
It's the same for alternate()
if you take Seshat, there are just two concrete classes, one doesn't even support the alternate()
method right now. The API does not care, if you implemented a mix()
method with a particular class or use an existing class.
It should preserve the desired units like ft, in
or h:min:s
whether storing them in a list, array, map or something else, that is implementation specific.
The whole term of "alternate unit" seems made up because alternate units of measure finds around 180k hits, but neither of them are the exact wording, most call it "alternative" and the majority are user groups for products by SAP or IBM. No scientific definitions. The most common term is "derived unit", something JScience4 also had. In fact Unit.alternate()
is totally unused by Indriya, the Units
system relies only on the constructor (or of()
as recently introduced) what about Seshat, does it use that somewhere? I could not see it used, just overridden by concrete classes.
We cannot simply delete it, but we could and should deprecate
it.
First of all, I feel the need to mention something different. These docs are good:
Using examples will always be beneficial to newcomers in our JSR, since if you're not coming from this field (as I do), it's sometimes more complex to understand the concept being discussed. So, let's keep adding useful examples to our docs, wherever possible.
I need to admit that given the description (first comment) of this issue it's a bit vague what we're voting for here (;maybe that's the reason noone else voted yet). However, given the title of the issue, I guess we're voting on the question "should we keep Unit.mix()
here?" which leads me to go for a thumbs up.
Why? Eventhough @desruisseaux has some valid points here regarding the conceptual patterns of interfaces and implementations and the way those are currently applied to our JSR, I still believe it's important to keep both MixedUnit
and Unit.mix()
here no matter of their usage frequency. Usage frequency should not affect an API's methods existence. An API should provide everything it should provide to the developer, no matter how frequently they might need each and every method, instead.
Of course, @keilw is right and we still need to resolve https://github.com/unitsofmeasurement/indriya/issues/200 in order to be fully compliant to our design, agreements, and strategy, but given that the former is already a registered (and actually ongoing) issue in indriya, my vote here clearly goes to keeping both MixedUnit
and Unit.mix()
here.
@unitsofmeasurement/experts can you please share your vote as well? We are close to our PR, so, we need to take a decision here soon.
It makes sense to have it in the Unit API. But the name ...“mix“ ... it make me think of a cooking recipe 😂. Ok for the class MixedUnit, but we should call the method differently, e.g Unit.with(Unit) FOOT_INCH = FOOT.with(INCH)
@thodorisbais, @dautelle: thanks for the feedback. As said before, I do not question the usefulness of "mixed units". I question the location of mix
method in the Unit
interface, instead of an equivalent mechanism (to be defined) in QuantityFormat
for example. The javadoc posted by Thodoris said "Mixed units can be used for formatting purpose": the way Unit.mix
is currently defined, it seems to be all about formatting (again this is okay, but in UnitFormat
or QuantityFormat
rather than in Unit
interface).
Can we identify some Unit
properties or behavior modified by Unit.mix
? I'm not yet aware of anyone:
FOOT
is identical than converting values from/to FOOT.mix(INCH)
.Unit.getSymbol()
and toString()
return the same text for FOOT.mix(INCH)
than for FOOT
(I presume).Quantity.getValue()
with getUnit()
needs to be interpreted as if mixed units does not exist.If we can not identify Unit
property or behavior that are modified by "mixed units", then I think that mixed units do not belong to the Unit
interface. Can we define a method in Quantity
or QuantityFormat
instead?
Again it has nothing to do with formatting alone, MixedUnit
and MixedQuantity
cover the entire spectrum of the API similar to what https://reference.wolfram.com/language/ref/MixedUnit.html allows you to do in Mathematica, What you said about formatting applies to alternate()
thus, either we keep both or deprecate alternate()
after all because we prefer to keep all of them implementation specific?
@dautelle I understand you may have strong feelings about the compound name, but we had a strong majority of experts and contributors vote for the name change under https://github.com/unitsofmeasurement/unit-api/issues/184, including two of the Spec Leads. The fact, that "compound unit" is the expression in less than 1% of all cases compared to "mixed units" being used anywhere from 3rd grade school classes to Mathematica makes this a more widely used term and it would be wrong to use something almost nobody would understand or associate with it.
again it has nothing to do with formatting alone,
Then, can you answer to the following questions?
FOOT
to FOOT.mix(INCH)
?Unit.getSymbol()
between FOOT
and FOOT.mix(INCH)
?Unit
method that behave differently between FOOT
and FOOT.mix(INCH)
?That is the subject of https://github.com/unitsofmeasurement/indriya/issues/200. The question is not so much about Unit methods, as for the Quantity.
Do you mean that above questions are implementation details? If this is the case, then Unit.mix
should stay in Indriya. If this method is defined in the Unit API, this its contract must be defined in the API.
The question is not so much about Unit methods, as for the Quantity.
I'm trying to identify in which aspects a Unit
instance for FOOT
differs from a Unit
instance for FOOT.mix(INCH)
. So far, the answer is "none". In that case, shouldn't the method be defined in Quantity
or QuantityFormat
and not in Unit
?
Should be solved in a consistent way with #186 .
I think we should keep "mixed unit" but rename the operator Unit.with(Unit) (instead of mix).
The only place with()
or withXYZ()
are used sometimes would be chaining arguments of a builder, but here it would just be meaningless compared to the other methods like prefix()
, alternate()
or transform()
. Each of them could be renamed to with()
just as well.
mix()
creates a mixed unit, what would with()
do, create a "withed unit"? There's been a strong consensus in #184 (6 "yes", including 2 Spec Leads and no strong objection) about calling it mix()
because compound() or "compound unit" is misleading and commonly referred to multiplication, also see places like https://binged.it/2uAi6yd
@dautelle : leaving the naming question apart, my issue with this proposal is whether Unit
is the right place for this functionality. Emphasis: I'm not against "mixed units", only against the location of a mix
method in the Unit
interface instead than an equivalent functionality in some Quantity
or Format
interface. If Unit
provided a mix
method, can you confirm the following statements would be true? (taking foot and inch as an example, but the same apply to any mixed units)
FOOT
to FOOT.mix(INCH)
would be the identity converter.Quantity.getValue()
returns a Number
in FOOT
; it does not make sense to said that it returns a value in FOOT.mix(INCH)
.Unit.getSymbol()
returns the same symbol for FOOT
and FOOT.mix(INCH)
.Unit
instance for FOOT
is identical in all aspects to an Unit
instance for FOOT.mix(INCH)
, except in the way quantities using that unit are formatted.FOOT
and FOOT.mix(INCH)
does not happen in Unit
, but in QuantityFormat
.@desruisseaux Once again, it is more than just formatting and while #54 has fewer votes in favor of an API method, annotate()
does precisely the same, yet nobody keeps saying, it is just for formatting and not meant to be a true Unit
which can be used for each and every operation units may work. Unit.getSymbol()
or rather MixedUnit.getSymbol()
is free for interpretation and discussion. IMO it could better be null as for every TransformedUnit
(yet again, if we don't want operations, this could also be subject to just formatting? ;-) The fact that there is a leadUnit() allows to behave like a Unit for the basic operations. And so does AnnotatedUnit. The conversion from FOOT
to FOOT.annotate("X)
leads to an identity converter, and we had AnnotatedUnit
in there at least since JSR 363 even though the method was not widely used enough to go into the API.
@keilw : annotated units like “Gal [Petroleum]” or “%vol.” said something about the quantities we are measuring. For example “%vol.” said that we are measuring percentage of volume, as opposed to percentage of mass. @dautelle gave a more "significant" example with "mile[standard]" versus "mile[US_survey]": in this case the conversion between those two units is about 1.000002 (and yes, this difference matter in Geographical Information System softwares). This is different than FOOT.mix(INCH)
: the fact that we write down a quantity as "foot and inch" is not a fundamental aspect of unit of measurement, only a writing convention.
Do we agree with the above two points?
@desruisseaux FOOT.mix(INCH)
creates a combination of these which must preserve the information about these two units, that's all. Whether you format them as foot, inch
, ft, in
or', "
that is the formatting aspect a particular implementation may chose to support in different flavors. One could see it as "array" or "list" of units which it is underneath the hood in most cases, but the mix()
contract mandates, that the logical combination of these units is preserved when you call mix. If an implementation like SIS, Seshat or others do not wish to support mixes unit operations, then so be it, that is not forced by the mix() method, one could just use it as a "formatting hint" but other implementations offer the full feature set https://reference.wolfram.com/language/ref/MixedUnit.html provides. And would a simple array of a Unit
or Quantity
be enough, then why would a 30+ year old product like Mathematica/Wolfram offer a MixedUnit in its Units API: https://reference.wolfram.com/language/guide/Units.html?
What Mathematica does can be a source of ideas, but no more. Arguments from authority are fallacy.
One could see it as "array" or "list" of units which it is underneath the hood in most cases, but the
mix()
contract mandates, that the logical combination of these units is preserved when you call mix.
Can you elaborate on that? Where the "array" or "list" aspect of mixed units become visible in the API? The only place I could see is in quantity formatting.
@thodorisbais , @otaviojava, @dautelle , others… I see votes but I still don't see answer to my question: why mixed units is considered a "fundamental aspect" of Unit
instead than a formatting aspect? I raised the following objections; what are the refutal to those objections?
Unit
or Quantity
behavior (method return value, conversion factors, etc.) is changed by mixed units, except formatting of Quantity
instances.Unit.annotate
can describe fundamental aspects like measurement methods (percentage of volume versus percentage of mass). Mixing units does not define any such fundamental aspects.Your assumption is wrong/incomplete, the "array" is used everywhere, just have a look at Indriya's MixedQuantityTest
. I understand that as the maintainer of one (or even two) implementations you may prefer to keep the changes to the API relatively low, but we want to offer real features for real users and applications. Not doing that properly is what got JSR 108 or 275 killed, remember. And yet, nearly 250k downloads happened for JSR 275 (which always contained that feature named compound()
, we also renamed a few other things since, e.g. Unit.shift()
) https://bintray.com/keilw/maven/javax.measure%3Ajsr-275#statistics while the Final API plus pre-releases of JSR 385 only came to roughly 100k downloads in the same timeframe (still quite impressive)
So by offering users of JSR 275 a migration path we may be able to reverse that some day and have 200k or 300k Unit-API downloads compared to 100k or fewer JSR 275 downloads.
If SIS
or Seshat
were able to cope with alternate()
without ever using it then they should also be able to cope with mix()
in the same way. Whether an implementation uses the full functionality inspired by Wolfram and other solutions or just sticks to formatting, that is up to any implementation.
The "array" is used everywhere, just have a look at Indriya's
MixedQuantityTest
This test seems to use Indriya-specific API. Does the Unit.mix(Unit)
proposal includes a proposal to put other Indriya's methods in the public API?
Unit.mix(Unit)
has no effect anywhere in the API. The fact that array is used everywhere under the hood does not matter from an API point of view.It is completed and Unit.mix()
has been implemented by Indriya. The additional functionality is left to other implementations, e.g. MixedUnit.getUnits()
, if say your implementations do nothing but assist formatting, then you may not expose this array there.
So far here there are 4 votes against 1, it is a democratic process, most others btw. are not yet done in the sense that they reached the minimum of 3 binding votes (you know that from. It is the same for Unit.alternate()
which returns an AlternateUnit
, in another implementation like Seshat the concrete class is called something else. We don't want to end up in a Brexit-like situation where no decisions are made. Unlike at least one or the other JSR here we vote on a democratic basis.
Answers to technical arguments are not a matter of vote! I have asked dozens of technical questions in this thread and got very few answers other than authority argument or "Indriya does this". Asking again:
Unit.mix(Unit)
the only method related to "mixed unit" in current proposal?Unit.mix(Unit)
has no visible effects on Unit
instances by anyway accessible through Unit API. Is that right?Unit.mix(Unit)
may have an effect on Indriya implementation, visible only through implementation-specific API. Then why do we need Unit.mix(Unit)
in the interface API if the result can be observed only through implementation-specific API?annotate
has been refuted in previous comment. Latest argument was about "array", but what is the fundamental measurement information in "2 feet 6 inches" compared to 2.5 feet? Note that I said "measurement information", not "formatting information".Can I get answers to those technical questions? No authority argument or "Indriya does this" please. This is not about objecting against any changes in the API; it is about having changes based on technical facts, not on feelings.
@dautelle leaving the exact method name aside, could you please help answer the rationale behind adding the Unit.compound()
method which was there for a long time, even before I joined as co Spec Lead?
As for 2, we must create a uniform way of accessing such methods, thus #186 would be the logical consequence of a decision that claims "do it in implementations only". That is not the case, but the 4 who voted in favor of the API method may change their vote till the end of the week when all the design votes shall conclude.
As for 3, while Indriya is the RI, ANY
compatible implementation should return an appropriate Unit instance that represents the "mixed" nature.
And the need for changes in the API is never driven by feelings, except maybe those by users who need a particular feature. Again the gigantic usage of JSR 275 compared to 363 and the current draft of 385 combined shows it meets a need by users. Which exact feature is used most is not so easy to determine, that's why we do such votes here. All implementation details are subject to a particular implementation
Any question or doubt, one may raise here applies exactly to #54, because both produce "hybrids" or "pseudo units" that behave like a unit but contain additional information. Calling MEGA(AMPERE.annotate("Annotation")
produces a string like "MA{Annotation}". Calling MEGA(DAY.mix(HOUR).mix(MINUTE)
results in a string representation like "Mday;h;min". Similar for many other operations. Thus @unitsofmeasurement/experts and @unitsofmeasurement/contributors anybody wishes to reconsider their votes? What shall we do with "mixed units", drop support altogether?
"Jigsaw" took many years longer, it was planned for Java 7 and only made it into Java 9. It still is far from perfect compared to e.g. OSGi, but it was added and provides some good. If we dropped mixed unit support because not every single operation of Unit may ultimately map to a mixed representation (for Quantity because the lead value summarizes the underlying parts it is actually easier) then we may have to abandon support for mixed units completely.
Compared to annotated units which I never really came across in a real life project most of us should know cases where either ft, in
or just h:min:sec
are used a lot.
Removing my "thumbs up"; it looks like these discussions are escalating to some big changes which we cannot afford given the short notice before our Final Release. I would like to invite @desruisseaux @dautelle for healthier collaboration, given the kind of arguments I have been reading for the past few days.
Let's work together to finalize everything on time :-)
Thanks, let's see, what others like @dautelle and @otaviojava say. The closer we get to a tie the more this would be a point where the Spec Leads can decide and if that also leads to a "let's not do it in this release", then like Jigsaw it'll have to wait. I cannot guarantee how soon but the idea is, while the JCP is still active and not stopped by Oracle one day, that an iterative follow-up makes sense especially for new bigger features like "value types" in the JVM.
As mentioned in #189 offering the formatting aspect only (it still must know how to format, this works 100% fine for both formatting and parsing by using the two types as Meta Information) could be a compromise, then I guess that would still work because throwing everything in the RI away once again or shelve it in a branch. In the end it's never just formatting because formatting takes an actual Quantity whatever concrete nature and also must allow t parse it. This is completely done, the only thing that's missing would be dissecting 1.4 feet into feet and inch.
However, without an API method all of this would be purely implementation specific. Unless e.g. QuantityFormat
could use instructions on parsing "mixed quantities", otherwise it is totally up to an implementation if that supports this kind of parsing and formatting or not. It is not the first time and like Prefix, maybe some day we found a way to make better use of this on an API level, but as @thodorisbais mentioned, the release should go out near World Metrology Day because then features and documentation we already got goes into effect and it would be a shame to miss that by months or even years just because other aspects take us as much time as e.g. Jigsaw did for its experts.
This CompoundQuantity behaved completely different, yet for a very similar purpose. It can be created from an array of matching quantities, thus the need of a CompoundUnit
or MixedUnit
was not given in that branch from what I remember. Composing the parts into a single matching Quantity of the same type is also possible. Formatting only via knowledge of that particular type in QuantityFormat implementations. Parsing hard to say, but it might work to do a parsing loop and then use them to create that "mixed quantity" (I would rather call it something like that because even Wikipedia clearly states "Other physical properties may be measured in compound units, such as material density, measured in kg/m3." So either MixedQuantity
, QuantityGroup
or similar should do, it is not a genuine Collection but it is backed by a Map
. @dautelle could you take a look and if you feel, this works better that way remove your thumb here, too? Unlike @thodorisbais right above it was a response to vote on the ticket, so it's still 3 votes, one above the minimum we consider reasonable here.
The term CompoundQuantity is consistent with Webster definition:
a quantity composed of two or more simple quantities or terms, connected by the sign + (plus) or - (minus).
For example: 1 ft + 2 in, 1 h + 30 min + 23 sec
Below the proposed addition to the API:
public interface Quantity<Q> {
CompoundQuantity<Q> with(Quantity<Q> lower);
...
}
public interface CompoundQuantity<Q> extends Quantity<Q> {
Quantity<Q> getHigher();
Quantity<Q> getLower();
}
CompoundQuantity<Duration> hhmmss =
factory.create(10, HOUR).with(
factory.create(12, MINUTE).with(
factory.create(34, SECOND);
Thanks for the pointer to Webster, that seems applicable because it only speaks of +
or -
, but let's keep in mind, there are many other definitions. If there was a need for an API level method I would go back to the original compound() rather than the more general with(), also matching what other methods are named, e.g. Quantity.to(Unit)
could frankly speaking also be called Quantity.with(Unit)
but we would not want to go there I assume.
The higher/lower
methods feel cumbersome compared to a getQuantities()
collection.
Before discussing an API level method, how does everyone (especially @desruisseaux, @andi-huber, @thodorisbais, @otaviojava or @filipvanlaenen) feel about extending Quantity for a "compound" element?
It has potential for conflict similar to the idea of a compound unit because the collection (regardless if it's an upper/lower pair or a set or list) of quantities would have a unit each but the parent would also have getValue()
and getUnit()
beside all the other methods it also would have per compound element.
The CompoundQuantity from the earlier branch or CompositeQuantity (take it as an alias right now) in the main branch only have to(Unit) in common with Quantity
, and via a "QuantityAccessor" or probably better something like QuantityConverter
there could be an API element that holds them together with Quantity
, but I am not sure about possible side-effects of directly extending Quantity
.
Having a look at the https://reference.wolfram.com/language/ref/MixedUnit.html, I've translated this into Java example code, honoring the current API proposal.
class MixedUnitDraft {
private static class USCustomary {
public static final Unit<Length> FOOT = Units.METRE.multiply(0.3048).asType(Length.class);
public static final Unit<Length> INCH = Units.METRE.multiply(0.0254).asType(Length.class);
}
@Test
@DisplayName("Quantity[MixedMagnitude[{1, 2}], MixedUnit[{'Feet', 'Inches'}]] -> 1'2''")
public void example1() {
// given
Unit<Length> mixedUnit = USCustomary.FOOT.mix(USCustomary.INCH);
Quantity<Length> mixedQuantity = Quantities.getMixedQuantity(new Number[] {1, 2}, mixedUnit);
// when
String formatedOutput = mixedQuantity.toString();
// then
assertEquals("1 ft 2 in", formatedOutput); // assuming symbols 'ft' and 'in'
}
@Test
@DisplayName("Quantity[MixedMagnitude[{1, 2}], MixedUnit[{'Feet', 'Inches'}]] "
+ "+ Quantity[10, 'Inches'] -> 24 in")
public void example2() {
// given
Unit<Length> mixedUnit = USCustomary.FOOT.mix(USCustomary.INCH);
Quantity<Length> mixedQuantity = Quantities.getMixedQuantity(new Number[] {1, 2}, mixedUnit);
Quantity<Length> increment = Quantities.getQuantity(10, USCustomary.INCH);
// when
Quantity<Length> sum = mixedQuantity.add(increment);
String formatedOutput = sum.toString();
// then
assertEquals("24 in", formatedOutput); // assuming symbols 'ft' and 'in'
}
@Test
@DisplayName("UnitConvert[Quantity[3.2, 'Days'], MixedUnit[{'Days', 'Hours', 'Minutes'}]] "
+ "-> 3 days 4 hours 48. min")
public void example3() {
// given
Quantity<Time> daysQuantity = Quantities.getQuantity(3.2, Units.DAY);
Unit<Time> mixedUnit = Units.DAY.mix(Units.HOUR).mix(Units.MINUTE);
// when
String formatedOutput = daysQuantity.to(mixedUnit).toString();
// then
assertEquals("3 days 4 hours 48. min", formatedOutput); // assuming symbols 'days', 'hours' and 'min'
}
}
Exercising these 3 examples, I cannot see any strong justification to have a concept of MixedUnits
at all. I believe, we could as well do all these examples with a powerful/convenient formatting API!
I'm confident saying this, because example2
already shows a major flaw of this 'mixed-radix' concept:
The motivation behind example2
might be to have a mixed-radix output, even after adding an increment
, but this already fails, where one ends up having to format the final calculation result to mixed-radix anyway.
I'm new to this topic, maybe I don't see the full picture. How is a MixedUnit superior to having
Quantities.getQuantity(1, USCustomary.FOOT)
.add( Quantities.getQuantity(2, USCustomary.INCH))
Thanks @andi-huber for those examples! Yes, it is also my point of view that MixedUnit
and MixedQuantity
are not needed at all and would actually cause more hurt than good, because e.g. they make the relationship between Quantity.getValue()
and Quantity.getUnit()
less straightforward. In this long thread, I have not yet seen any example of MixedQuantity
information that can not be calculated in the way you gave in above post.
Parsing mixed/compound quantities do not require any change in the API because we can just make QuantityFormat
implementation more advanced, so when the parser see "1 ft 2 in" it automatically compute the expression you gave in above post.
Formatting mixed/compound quantities could be done with an API like below in QuantityFormat
(may need a better method name than asCompound
):
void <Q extends Quantity<Q>> asCompound(Unit<Q> source, Unit<Q>... target);
For example format.asCompound(USCustomary.FOOT, USCustomary.FOOT, USCustomary.INCH)
could mean: when formatting a quantity using foot units, format it as "x ft y in".
Above API proposal may not be satisfying and we can probably do better, but it show the idea.
If someone wants the numerical values of compound quantity for other purpose than formatting, it can be done by a method like that in the Quantity
interface:
double[] getCompoundValues(Unit<Q>… units);
So if a quantity represents 14 inches, invoking getCompoundValues(USCustomary.FOOT, USCustomary.INCH)
would return {1, 2}
. Those values can be computed on-the-fly, I don't see any need for storing them in a new MixedQuantity
or CompoundQuantity
interface.
It would mix the calculation a bit but compared to all the sometimes weird and over-freighted methods in classes like LocalDateTime
I guess a method like Number[] getCompoundValues(Unit<Q>… units);
in Quantity
could also do the job.
Putting void <Q extends Quantity> asCompound(Unit
source, Unit
... target); into
QuantityFormat
I strongly oppose, that would mix formatting and value transformation. If we found another place either Quantity
or QuantityFactory
, sure, see #187.
Okay about avoiding to mix formatting and unit conversion. That is why I said that the QuantityFormat.asCompound
proposal needs refinement. Note however that in my mind, formatting "1.5 ft" as "1 ft 6 inch" is not a unit conversion (contrarily to formatting the value in meters, which would be a conversion). A possible amendment would be:
asCompount(Unit unit, Unit... fractionalComponents)
Example: asCompound(USCustomary.FOOT, USCustomary.INCH)
would mean "for quantities expressed in foot unit, express the fractional part in inches". The difference compared with my previous proposal is that the FOOT
component is not repeated; this method signature does not allow conversion of FOOT
in another units - it only change the way the fractional part is formatted.
QuantityFormat
must not return a Quantity
except when parsing. The whole idea of parsing will not work at all, otherwise we must have some value holder after all, or would you convert to a single Quantity
with parseCompound(String)
?
Parsing of "1 ft 2 in" returns a single Quantity
, exactly in the way Andy has shown in his comment (the last block of code).
What about asCompound
or createCompound
for #187 ? Would we offer both? For the sake of simplicity I'd keep the discussion about Compound formatting/parsing in this ticket, although it is more about Quantity
than Unit
, but if a compound value representation should exist, that makes no sense in QuantityFormat
, it would have to be either in Quantity
itself or under certain circumstances in QuantityFactory
.
Taking your suggestions and adding some sugar, I've converted the 3 examples from above into a draft, without the need of any MixedUnit
.
It introduces MixedRadix
to replace MixedUnit
, with MixedRadixFormat
just being a stub.
class MixedUnitDraft2 {
private static class USCustomary {
public static final Unit<Length> FOOT = Units.METRE.multiply(0.3048).asType(Length.class);
public static final Unit<Length> INCH = Units.METRE.multiply(0.0254).asType(Length.class);
}
// -- MIXED RADIX PROTOTYPE
private static class MixedRadix<Q extends Quantity<Q>> {
public static <X extends Quantity<X>> MixedRadix<X> ofPrimary(Unit<X> unit) {
return new MixedRadix<>(unit, Collections.emptyList());
}
private final Unit<Q> primaryUnit;
private final List<Unit<Q>> fractionalParts;
private MixedRadix(Unit<Q> primaryUnit, List<Unit<Q>> fractionalParts) {
this.primaryUnit = primaryUnit;
this.fractionalParts = fractionalParts;
}
public MixedRadix<Q> mix(Unit<Q> fractionalPart) {
List<Unit<Q>> fractionalParts = new ArrayList<>(this.fractionalParts);
fractionalParts.add(fractionalPart);
return new MixedRadix(primaryUnit, fractionalParts);
}
}
// -- MIXED RADIX FORMAT PROTOTYPE
private static class MixedRadixFormat implements QuantityFormat {
public static MixedRadixFormat of(MixedRadix<?> mixedRadix) {
return new MixedRadixFormat(mixedRadix);
}
private final MixedRadix<?> mixedRadix;
private MixedRadixFormat(MixedRadix<?> mixedRadix) {
this.mixedRadix = mixedRadix;
}
@Override
public Appendable format(Quantity<?> quantity, Appendable destination) throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String format(Quantity<?> quantity) {
// TODO Auto-generated method stub
return null;
}
@Override
public Quantity<?> parse(CharSequence csq, ParsePosition pos)
throws IllegalArgumentException, MeasurementParseException {
// TODO Auto-generated method stub
return null;
}
@Override
public Quantity<?> parse(CharSequence csq) throws MeasurementParseException {
// TODO Auto-generated method stub
return null;
}
}
// -- EXAMPLES
@Test
@DisplayName("Quantity[MixedMagnitude[{1, 2}], MixedUnit[{'Feet', 'Inches'}]] -> 1'2''")
public void example1() {
// given
MixedRadix<Length> mixedRadix = MixedRadix.ofPrimary(USCustomary.FOOT).mix(USCustomary.INCH);
Quantity<Length> lengthQuantity = Quantities.getQuantity(1, USCustomary.FOOT)
.add(Quantities.getQuantity(2, USCustomary.INCH));
// when
QuantityFormat format = MixedRadixFormat.of(mixedRadix);
String formatedOutput = format.format(lengthQuantity);
// then
assertEquals("1 ft 2 in", formatedOutput); // assuming symbols 'ft' and 'in'
}
@Test
@DisplayName("Quantity[MixedMagnitude[{1, 2}], MixedUnit[{'Feet', 'Inches'}]] "
+ "+ Quantity[10, 'Inches'] -> 24 in")
public void example2() {
// given
MixedRadix<Length> mixedRadix = MixedRadix.ofPrimary(USCustomary.FOOT).mix(USCustomary.INCH);
Quantity<Length> lengthQuantity = Quantities.getQuantity(1, USCustomary.FOOT)
.add(Quantities.getQuantity(2, USCustomary.INCH));
Quantity<Length> increment = Quantities.getQuantity(10, USCustomary.INCH);
// when
Quantity<Length> sum = lengthQuantity.add(increment);
QuantityFormat format = MixedRadixFormat.of(mixedRadix);
String formatedOutput = format.format(sum);
// then
//assertEquals("24 in", formatedOutput); // assuming symbols 'ft' and 'in'
//UPDATE
assertEquals("2 in 0 ft", formatedOutput); // assuming symbols 'ft' and 'in'
}
@Test
@DisplayName("UnitConvert[Quantity[3.2, 'Days'], MixedUnit[{'Days', 'Hours', 'Minutes'}]] "
+ "-> 3 days 4 hours 48. min")
public void example3() {
// given
Quantity<Time> daysQuantity = Quantities.getQuantity(3.2, Units.DAY);
MixedRadix<Time> mixedRadix = MixedRadix.ofPrimary(Units.DAY).mix(Units.HOUR).mix(Units.MINUTE);
// when
QuantityFormat format = MixedRadixFormat.of(mixedRadix);
String formatedOutput = format.format(daysQuantity);
// then
assertEquals("3 days 4 hours 48. min", formatedOutput); // assuming symbols 'days', 'hours' and 'min'
}
}
Based on what was done in JSR 275 earlier, both the now
MixedUnit
and aUnit.mix()
"fluent" factory (we had Fluent API long before it became hip ;-) were applied to JSR 385.While nobody questions that these model elements are needed, even if you just want to format
5' 2"
or similar, at least @desruisseaux raised doubts, if the use case is strong enough to put it into the API ofUnit
. A viable alternative would beMixedUnit.of(Unit...)
in the RI, but that would be implementation specific. We moved other common elements likeMetricPrefix
here and theUnit.prefix()
method allows to use them in a platform and implementation-neutral way now.I believe, Unit.annotate() is quite "exotic" or academic so similar to this question,
AnnotatedUnit.of()
could be a better approach, but mixed units are a very common use case not just in the US. I had a client project with JSR 363 where common container sizes like8ft 6ins
or7ft 7ins
had to be modeled in a rather awkward way usingQuantityRange
. It is fairly common, so I think it makes at least as much sense as the ones already here like alternate() or transform(). Each of them would also have worked withAlternateUnit.of()
instead ofUnit.alternate()
.