Closed mpusz closed 1 year ago
If you prefer #1
, please give a thumbs up here.
If you prefer #2
, please give a thumbs up here.
Please share your comments below.
Greetings! I haven't followed this discussion, but if I had to pick a syntax, it would definitely be (2). I wrote most of P2128's discussion about preferring to reserve operator[]
for array access or things like it.
@mhoemmen, whichever syntax we choose here the operator[]
still will be used to specify a unit for quantity in many places because it is a common way to type that. For example:
quantity<isq::speed[m / s], int> speed{42};
auto distance = quantity_cast<isq::distance[km]>(isq::length(42, m));
quantity_of<isq::altitude[m]> auto alt = get_alt();
Additionally, it seems that even if we choose option 2, the following will be valid:
auto speed = isq::speed[km / h](42);
After the discussion in https://github.com/mpusz/units/pull/391#discussion_r1056327563 we consider the above to be the primary syntax for now and then possibly add a "comma syntax" as a shorthand wrapper to do the same in one step.
I guess I dislike both variants of creating quantities through the combination of a Reference with a Number (that is, both 42 * isq::speed[km / h]
and isq::speed[km / h](42)
look wrong to me). This is probably rooted in dimensional analysis - to me, units are mostly a representation "artefact" and do not matter until I need to specify a number for a quantity. Because of that, I want to always keep number and unit very close together, and (visually) parsing the construction of a Reference before I encounter the number is distracting to me.
I see your point. We can discuss some other syntaxes as well here if someone has some ideas... At this point, we can still change everything, and we are still looking for the best way to define and construct a quantity and to specify other related properties (quantity_of<>
concept, quantity_cast
, etc). With the current proposal, I think it is good that we can clearly separate a representation type and the quantity value from its quantity-specific properties. As ISO 80000 specifies, "a quantity is a reference and a number" (https://mpusz.github.io/units/glossary.html#term-quantity).
As you write in the glossary, a unit can be a reference. So what if we restore the unit references that we have in the library now, such that 42 * km
would be a perfectly valid way to specify a length? Then, we could make isq::speed(42 * (km/h))
and isq::velocity(42 * (km/h))
essentially an act of "down-casting"/narrowing of the reference, adding further properties to the quantity. The upshot would be that in the many cases where the unit is all we need, the comparatively verbose spelling of the quantity spec can be avoided (it is implied by the unit reference).
It would have the effect that the following two would be equivalent ways to specify a radioactivity: isq::radioactivity(1 * (1/s))
and 1 * Bq
, but isq::radioactivity(1 * Hz)
would be disallowed (radioactivity is incompatible with frequency). I would further tend to disallow up-casting (as in isq::time(1 * (1/Hz))
) but leave that to quantity_cast
. Cross-casting should probably be prohibited even for quantity_cast
(between frequency and radioactivity), but can be achieved using double-cast.
That is an interesting idea, but there are still some issues to be solved here. For example, what should 42 * J
mean? ISO 80000 specifies at least the following quantities to be measured in joules:
potential_energy
kinetic_energy
mechanical_energy
mechanical_work
heat
latent_heat
energy (thermodynamics)
internal_energy
enthalpy
Helmholtz_energy
Gibbs_energy
signal_energy_per_binary_digit
Another issue is that isq::velocity(42 * (km/h))
needs to have a vector representation type. Of course, we can allow int
to be a vector in a special case, but by default, it should not work. On the other hand, we cannot create a length with a vector representation type. So how to create a velocity and position_vector here? vector{1, 2, 3} * km
will not compile because it is not a length
. isq::velocity(42 * (km/h))
will not compile because 42
is not a vector representation type.
A lot of the derived quantities are not created in terms of base quantities but in terms of specific kinds, so it might look nice in the case of speed
, but I am afraid that in most other cases, it will be really verbose and confusing.
Another case here is the natural units system. In such case 42 * second
may mean both a length
and a time
if you assume c = 1
. It turned out that one simply cannot reason about a quantity type having just a unit.
Given a hierarchy of quantity types as currently under discussion in #405, 42 * J
would create a quantity of the base/root type covering all quantities of dimension $M L^2 T^{-2}$. That type may be named <energy_dim>
("aliased" - while not necessarily the same C++ type, it should be considered equivalent for all purposes by the machinery of the library). Here, <energy_dim>
is whatever name the SI gave that dimension - if there is no SI name for that dimension, then that base/root type may just be a "derived"/"generated" type.
Then, by our tentative conclusion that narrowing_quantity_type_cast
should be allowed implicitly, 42 * J
would be acceptable by all those quantities you listed. Of course, the expression isq::heat(42 * J)
for example would trigger that cast. Once down-casted, it would potentially require explicit casts to go up the hierarchy again, so attaining full "quantity-safety" through type-safety. How far that restriction shall go is of course another point of discussion (-> #405), but the machinery could work this way in any case.
As one of the more special cases, the expression 42 * Hz
would not create a quantity of the base/root type of dimension $T^{-1}$, but rather a quantity of type frequency
. isq::radioactivity(42 * Hz)
would be definitely disallowed by all sane implementations (horizontal casting), but explicit up-casts to isq::inverse_time
will remain allowed. However, we can still freely choose which syntaxes we allow for those up-casts, such that isq::inverse_time(42 * Hz)
remains forbidden and leave (widening_)quantity_(type_)cast<isq::inverse_time>(42*Hz)
as the only option.
As for the natural units system, the machinery works equally well. 42 * s
could create a quantity of the base/root type of dimension $T$ (the only dimension there is), compatible with all kinds, including length
and time
. You could then also define a reference light_second = isq::length[s*c]
, which directly creates quantities of kind length
(and dimension $T$). Again, if length
and time
should refer to incompatible kinds or compatible types is probably the point of another discussion.
What do you think?
That is an interesting idea. However we have to think what it means from the interface point of view. I think that it makes quite a lot of mess here. But let's start from the beginning.
42 * J would create a quantity of the base/root type covering all quantities of dimension . That type may be named
("aliased" - while not necessarily the same C++ type, it should be considered equivalent for all purposes by the machinery of the library). Here, is whatever name the SI gave that dimension - if there is no SI name for that dimension, then that base/root type may just be a "derived"/"generated" type.
If I understand correctly you suggest 42 * J
to directly create quantity<isq::energy[J], int>
. However, J = kg⋅m2⋅s−2 = N⋅m = Pa⋅m3 = W⋅s = C⋅V
. No matter which unit we will select to construct such a quantity we should end up in the same or at least 100% compatible type. When we follow that way of thinking Hz = 1/s
and this should be the same case here so we should not do the following:
As one of the more special cases, the expression 42 * Hz would not create a quantity of the base/root type of dimension , but rather a quantity of type frequency
As for the natural units system, the machinery works equally well.
It depends how would you like to define a natural unit system. Even though the speed is dimensionless and expressed in unit 1
, I prefer the option where length and time are both "strong" dimensions and speed can be constructed only with length / time
and not length / length
or time / time
. This would not be possible here if I am not wrong.
Now let's try to think how we can define a quantity type. To be consistent with proposed behavior should quantity<J, int>
be a valid syntax? What quantity_spec
should be used in such a case? Moreover, we still have to support things like quantity<potential_energy[J], int>
for specific kinds of quantity.
Next one is quantity_of
. If I type quantity_of<J>
should it just check a unit or also a quantity_spec
? The same question should be answered for quanity_cast<J>(q)
. Does it cast only a unit or a quantity_spec
as well.
If we like the above idea, I have the following proposal. 42 * J
is a shortcut for typing 42 * (isq::mass * pow<2>(isq::length) / pow<2>(isq::time))[J]
and user can choose either of those. As a result, we get quantity<reference<derived_quantity_spec<isq::mass, power<isq::length, 2>, per<power<isq::time, 2>>>{}, si::joule>{}, int>
(not a quantity<reference<isq::energy, si::joule>{}, int>
). However, as we agree that quantities should implicitly downcast, this will be able to convert to a quantity of energy to improve compile-time errors and interfaces. If the user would like ot have isq::energy
right away, the following will have to be used 42 * isq::energy[J]
.
This new syntax will deduce a quantity_spec
only for a construction helper and will not work for quantity
type, or quantity_of<>
concept, or quantity_cast<>()
.
Additionally, number * reference
will be the only helper to construct a quantity, and the other syntaxes of quantity_spec(number, unit)
and reference(number)
will be removed to not produce too many inconsistent interfaces.
Last but not least, this means that we will not be able to simplify units definitions from:
inline constexpr struct hour : named_unit<"h", mag<60> * minute> {} hour;
to
inline constexpr struct hour : named_unit<"h", 60 * minute> {} hour;
if the latter will start to be allowed by the C++ language.
How do you like such an approach?
Some code examples:
quantity_of<isq::energy> auto q1 = 42 * isq::energy[J];
weak_quantity_of<isq::energy> auto q2 = 42 * J;
quantity_of<isq::energy> auto q3 = 42 * J + 42 * isq::energy[J];
quantity<isq::energy[J], int> q4 = 42 * J + 123 * (kg * m2 / s2);
quantity<isq::energy[J], int> q5 = 40 * N * (20 * m) + 123 * (kg * m2 / s2);
Also, as @JohelEGP correctly noticed in https://github.com/mpusz/units/pull/391#discussion_r1055375095, the multiply syntax has some issues. So we have to choose the best compromise here.
If we like the above idea, I have the following proposal. 42 J is a shortcut for typing 42 (isq::mass pow<2>(isq::length) / pow<2>(isq::time))[J] and user can choose either of those. As a result, we get quantity<reference<derived_quantity_spec<isq::mass, power<isq::length, 2>, per<power<isq::time, 2>>>{}, si::joule>{}, int> (not a quantity<reference<isq::energy, si::joule>{}, int>). However, as we agree that quantities should implicitly downcast, this will be able to convert to a quantity of energy to improve compile-time errors and interfaces. If the user would like ot have isq::energy right away, the following will have to be used 42 isq::energy[J].
That is exactly what I am proposing. derived_quantity_spec<isq::mass, power<isq::length, 2>, per<power<isq::time, 2>
is that root of the quantity type hierarchy of said dimension.
When we follow that way of thinking
Hz = 1/s
I believe we have some freedom here. After all, we have already established that 1/s
shall not be 100 % equivalent to Hz
, in that "Hz
shall only every be used for a frequency" (I think I'm quoting @JohelEGP here from somewhere). And that seems also what ISO 80000 says.
Additionally,
number * reference
will be the only helper to construct a quantity, and the other syntaxes ofquantity_spec(number, unit)
andreference(number)
will be removed to not produce too many inconsistent interfaces.
Sounds reasonable. Could we still have the quantity_spec(quantity)
as a syntax for that narrowing_quantity_type_cast
? Personally, I prefer isq::energy(42 * J)
over 42 * isq::energy[J]
.
Last but not least, this means that we will not be able to simplify units definitions
Why is that? By definition above, 60 * minute
is a quantity
, but with #411, it is acceptable as a NTTP. We should be able to create a template alias named_unit<Symbol,Quantity>
that can construct whatever we want it to based on Quantity
. I must be missing something, as I don't see how the latter could be disallowed even today if the former is allowed.
Also, as @JohelEGP correctly noticed in https://github.com/mpusz/units/pull/391#discussion_r1055375095, the multiply syntax has some issues.
I don't feel that these issues are all that relevant with this shorter form. If you still support isq::energy(42 * J)
, then this form is exactly equivalent to the form isq::energy(42, J)
that was proposed to solve the perceived issues - with the exception of 5) "the amount of *
required". Furthermore, the short form, while it does require those parentheses for associativity, all alternatives do also require parentheses as part of the syntax. The improvement of isq::energy(42, J)
over (42 * isq::energy[J])
is that it removes that second set of parentheses. Parentheses however can indeed be omitted in some cases, which is a a slight disadvantage of the short form.
The SI brochure says
(d) The hertz shall only be used for periodic phenomena and the becquerel shall only be used for stochastic processes in activity referred to a radionuclide.
ISO 80000-1, 3.9 unit of measurement, note 2 includes
However, in some cases special unit names are restricted to be used with quantities of specific kind only. For example, the unit second to the power minus one (1/s) is called hertz (Hz) when used for frequencies and becquerel (Bq) when used for activities of radionuclides. Another example is joule (J), used for energy, but never for moment of force, the unit of which is newton metre (N•m).
I believe we have some freedom here. After all, we have already established that 1/s shall not be 100 % equivalent to Hz, in that "Hz shall only every be used for a frequency"
I am not sure if we will be able to implement that in a generic way that will not look like a workaround.
I don't see how the latter could be disallowed even today if the former is allowed.
The former just scales a unit providing another unit. It does not know anything about the dimension or a reference type.
quantity
is defined in terms of reference
, which in turn is defined in terms of unit
. That is why I think a unit
should not be defined in terms of a quantity
again. Even if we find a hack to make it work, it will look like a bad design. Also in compiler errors, such a unit will look bad/long. Another point here is that Representation
is not able to support all of the ratios.
with the exception of 5) "the amount of * required"
This and also the number of additional parentheses needed in the short form. For example: 42 * m / (2 * (m / s))
. With the design proposed here we had isq::length[m](42) / isq::speed[m / s](2)
. Longer but less *
and ()
.
Another example is joule (J), used for energy, but never for moment of force, the unit of which is newton metre (N•m).
As long as I can see that we can limit J
to energy I do not see an easy way to limit N * m
to moment of force :-(
Could we still have the quantity_spec(quantity) as a syntax for that narrowing_quantity_type_cast? Personally, I prefer isq::energy(42 J) over 42 isq::energy[J]
I am not sure. quantity_spec(quantity)
will be another thing to standardize and learn. Although, I must admit it looks quite nice to me as well.
To simplify, we could remove 42 * isq::energy[J]
syntax, but we still need to preserve isq::energy[J]
for the quantity
definition, quantity_of
, and quantity_cast
, so I do not think that is a good idea. Also removing it but leaving isq::energy[J](42)
or isq::energy(42, J)
is totally inconsistent with proposed 42 * J
.
I don't share your concerns regarding the implementation: With the proposed syntax, unit
instances behave like references
in some circumstances. However, a reference
is just a designator for a quantity_spec
and a unit
. The quantity_spec
is a narrowing of the concept of a physical dimension (it adds more information), and as I have been trying to point out in the last few days, that narrowing is best though of as a hierarchy (event if it adds "orthogonal" information - it still adds information). The root of that hierarchy is then the quantity_spec
with no information added beyond the dimension (which is already in the unit
), thus references
at the root level are 1:1 with units. Even your glossary says "a reference can be a unit". With that view, only_for<...>
units then become equivalent to references which are not at the root of the hierarchy, but rather at the root of the sub-hierarchy where the narrowed quantity_spec
is anchored.
Once units
are considered references
, we achieve a nice symmetric set of syntax:
quantity_spec[reference] -> reference
: Narrows the quantity_spec
of the given reference
.number * reference -> quantity
: Creates a quantity
.quantity_spec(quantity) -> quantity
: Narrows quantity_spec
of the given quantity
.quantity[unit] -> quantity
: Essentially quantity_cast<unit>(quantity)
.quantity[reference] -> quantity
: Essentially quantity_cast<reference>(quantity)
.The two remaining syntaxes are:
reference(number)
: I don't like this one.quantity_spec(number, unit)
: This one looks almost the same as quantity_spec(number * reference)
, and does the same thing. I think it's not needed, but harmless. If we keep it, I would make them exactly equivalent.Even your glossary says "a reference can be a unit".
Indeed. The definition of quantity contains
NOTE 2 A reference can be a measurement unit, a measurement procedure, a reference material, or a combination of such.
But in mp_units
, as I understand it, a reference is an instance of an unit with the additional information of the system of units the reference is part of. So a unit is a generic specification, and a reference an instance of a unit for a given system of units. The idea is to not have to repeat the unit specification for each system of units it is part of. So "m" may be just "metre", the "metre" in SI, or the "metre" in the natural system of units.
So in mp_units
a reference is more than just an unit in order to support multiple systems of units.
I imagine some quantities could also be shared between different system of units. So if mp_units
were to also treat a quantity_spec
like an unit
, i.e. as not part of any system of units, a reference
would be necessary to know the system of units the quantity is part of.
But in mp_units, as I understand it, a reference is an instance of an unit with the additional information of the system of units the reference is part of.
I understood that a reference is really just a quantity_spec
together with a unit
, and the concept of a "system of units" is not represented at all. That, combined with quantity_spec
not storing a direct relation to a "system of unit" would make a "reference
to a root quantity_spec
" equivalent to a unit as I described above. But I could be wrong here.
If we indeed need to specify an explicit relation to a "system of units", and do not want to include that relation in the unit
because we want to share the unit
between "systems", then, we obviously need to combine two separate pieces of information to form a reference
. A number * unit
then form then lacks that "system" information, and a number * unit
would be a new concept, different from number * reference = quantity
. I believe we don't want yet another concept whose purpose is just to be converted to a quantity
once "system of units" information becomes available; that indeed feels like a hack.
Then, w have two options: The first option is indeed, we require the user to always provide create a reference through one of the syntaxes that were under discussion initially. Or we could provide real references
that look like units
(e.g. static constexpr auto m = isq::length[isq::metre];
) but do carry that "system of units" information, and thus enable the syntax and semantics that I described. The disadvantage is that now, implementing a new unit requires at least two steps: Creating the actual unit
, and creating a matching reference
for each system that should be supported.
Given that both options seem suboptimal to me, let me ask why we even need that "system of units" information, beyond what is already encoded (perhaps implicitly) in the base dimensions of the units we use? Unless my initial understanding was correct all along, and there is no such separate information in current V2 at all.
Just to make it clear, systems of units are built on top of systems of quantities, and those are built by manipulating base dimensions. As @JohelEGP mentioned some time ago, there is no such thing that would be named as a dimension of energy
. There is only a corresponding derived dimension built from base dimension ingredients. This is one additional reason why we have quantity_spec
to provide strong names for derived things.
thus references at the root level are 1:1 with units.
No. As I mentioned by definition J = kg⋅m2⋅s−2 = N⋅m = Pa⋅m3 = W⋅s = C⋅V
or Hz = 1/s
. All of those are the same units but not necessary the same quantities.
the quantity_spec with no information added beyond the dimension (which is already in the unit)
In V2 only base units get information about the base dimensions they are used to measure. This is done to be able to verify if a provided unit is compatible with the current quantity but yes it could work as well as some kind of a reference if we would redesing the framework a bit.
we achieve a nice symmetric set of syntax:
It is a bit different from what we have right now but I can image it to work like that. I am only afraid that with such a logic people will be too eager to just pass units everywhere which will end up in hard to understand (read long) error messages as we will always talk in terms of derived quantities equations rather their names. So we are trading compile time error readability for quantity construction helper robustness here.
But in mp_units, as I understand it, a reference is an instance of an unit with the additional information of the system of units the reference is part of. So a unit is a generic specification, and a reference an instance of a unit for a given system of units. The idea is to not have to repeat the unit specification for each system of units it is part of. So "m" may be just "metre", the "metre" in SI, or the "metre" in the natural system of units.
This was the case in V1. In V2 I didn't find a different way then to define a unit in terms of a system of quantities, to be able verify the correctness of the assigned unit to a specific quantity
I imagine some quantities could also be shared between different system of units.
Yes, this is the main idea of V2. Many systems of units (even natural units system) (for example SI, CGS, FPS) should be able to reuse the same definition of a system of quantities (ISQ).
That, combined with quantity_spec not storing a direct relation to a "system of unit"
That is the main intent here. As stated above quantity_spec
defines the "system of quantities" part. When joined with a unit (representing a system of unit) it creates a reference that provides all "domain-specific" knowledge to a number.
A number unit then form then lacks that "system" information, and a number unit would be a new concept, different from number * reference = quantity. I believe we don't want yet another concept whose purpose is just to be converted to a quantity once "system of units" information becomes available; that indeed feels like a hack.
This is what I was initially afraid of.
The disadvantage is that now, implementing a new unit requires at least two steps: Creating the actual unit, and creating a matching reference for each system that should be supported.
This was the case for V1 refrences.
This is what I meant by trading compile-time errors for quantity construction helpers' robustness:
static_assert(is_of_type<60 * (m / s), quantity<reference<derived_quantity_spec<isq::length, per<isq::time>>, derived_unit<si::metre, per<si::second>>>{}, int>>);
static_assert(is_of_type<60 * isq::speed[m / s], quantity<reference<isq::speed, derived_unit<si::metre, per<si::second>>>{}, int>>);
static_assert(is_of_type<42 * J, quantity<reference<derived_quantity_spec<isq::mass, power<isq::length, 2>, per<power<isq::time, 2>>>{}, si::joule>{}, int>>);
static_assert(is_of_type<42 * isq::energy[J], quantity<reference<isq::energy, si::joule>{}, int>>);
I am worried that people will end up with the first types way too often if we provide such a "lazy" feature.
We can also discuss if quantity<si::joule, int>
should be a valid syntax. This is ground-breaking for mp-units but with the logic provided by @burnpanck in https://github.com/mpusz/units/issues/413#issuecomment-1374911713, it could work.
If we agree on the above, then how does it relate to #405? The above simplifies the library to the maximum. The #405, on the other hand, wants to add a lot of additional information. The biggest problem with the latter is the fact that we do not know what to do about that additional data. As we discussed, quantity character types are controversial, and we do not know how to enforce quantity kinds in an efficient way.
The above simplifies the library to the maximum.
How does that formulation handle the following?
In one extreme: Nothing is allowed. Everything is in the user's hands, including making sure not to mix quantities of the same dimension in ways that shouldn't be mixed due to properties not expressible by the library.
There's no one other extreme, as evidence by the necessity of making tradeoffs.
Playing on the interpretation of a unit
as a reference
denoting a quantity "of that unit" (i.e. no further restriction than what is encoded in the unit specification), we could support both by allowing both quantity<si::joule, int>
as-well as quantity<isq::energy[si::joule], int>
. These could remain interoperable using the implicit narrowing and widening rules I described. This assumes that the quantity type concept (or whatever we want to call it) forms a hierarchy. Then, the answers are as follows:
inline constexpr struct moment_of_force : compatible_narrowing<isq::force * si::length> {} moment_of_force;
. Here, si::length
is the quantity type describing quantities of the SI dimension length.In this design, the quantity type-safety is enforced by the C++ type system to a user-selectable level: The user chooses how narrow/"far down the tree" they want to specify their quantities; since quantities don't implicitly widen, the user is softly "locked-in" unless overridden with explicit widening. The preferred way to "start-off" is by constructing quantities using an instant narrowing of pure units-based quantities: isq::energy(42 * J)
. However, in cases where a strong type is hand-written (such as a state of a Kalman filter), narrowing does not have to be repeated and a more convenient initialisiation with just 42*J
is possible.
How does that formulation handle the following?
The problem I see here is a question if anyone will be using quantity<isq::energy[si::joule], int>
if quantity<si::joule, int>
will virtually mean the same thing. By that, I mean that the quantities of compatible kinds may be added/subtracted and compared to each other anyway. So what would be the benefit of having different quantities of the same dimension? I am not fun of a unit-only approach but looking at it realistically, what do we lose in terms of type-safety if we follow this path for the SI system (natural units systems aside)?
I think that the problem here is that we already know how to specify a hierarchy of quantities or specify their character, but we really do not know how to benefit from that information in an efficient way in the C++ language. The only thing we agreed on so far in #405 is that we should probably not allow two quantities of parallel kinds to be compatible with each other. Other than that, there are still controversies if we should somehow enforce the characteristics of a quantity or limit conversions to vertical kinds.
Here, si::length is the quantity type describing quantities of the SI dimension length.
I am against mixing or cross-referencing systems of quantities with systems of units. ISQ should not use or specify any units (just quantity types/specs). SI, CGS, and other systems if units should build on top of ISQ to assign units to quantities. We had quantities redefined for systems of units (i.e. SI) in V1, and it was too expensive from the definition and standardization point of view.
Horizontal could be disallowed completely, or allowed explicitly when moving within the same "compatible" subtree.
This is what we agreed on but even though I really do not like it, ISO 80000-1:2022 says explicitly:
For example, while the diameter of a cylindrical rod can be compared to the height of a block, the diameter of a rod cannot be compared to the mass of a block.
So what would be the benefit of having different quantities of the same dimension?
Depends on what the library would allow. If nothing else, it allows expression of intent.
For example, there are pairs of quantities of equal dimension where one is a factor of 2∏ of the other. So if it wasn't possible to express different quantities of the same dimension, and you chose to stick to what the library allows, you could accidentally mix those without taking into account the factor of 2∏. If it was possible to express different quantities of the same dimension, accidental conversion could be avoided. Furthermore, it opens the path to easing the burden of conversion and book-keeping of the factor of 2∏ required on the part of the user.
Horizontal could be disallowed completely, or allowed explicitly when moving within the same "compatible" subtree.
This is what we agreed on but even though I really do not like it, ISO 80000-1:2022 says explicitly:
For example, while the diameter of a cylindrical rod can be compared to the height of a block, the diameter of a rod cannot be compared to the mass of a block.
As you mention, it's a matter of safety and ease of use. Comparisons between quantities of the same kind that are not in the same vertical hierarchy of kind could be allowed if there are no safety concerns.
If comparing diameter and height should consider the length, what of the following should be true
? Remember that radius is half of a diameter.
radius(2, m) == diameter(2, m)
.radius(1, m) == diameter(2, m)
.height(2, m) == diameter(2, m)
.height(2, m) == radius(2, m)
.height(2, m) == radius(1, m)
.height(1, m) == radius(2, m)
.height(1, m) == radius(1, m)
.Also remember that there's a relation between comparison and conversion required by the std::regular
concept. And that cross-type comparisons require a std::common_reference_t
to model std::equality_comparable_with
.
I am against mixing or cross-referencing systems of quantities with systems of units. ISQ should not use or specify any units (just quantity types/specs). SI, CGS, and other systems if units should build on top of ISQ to assign units to quantities. We had quantities redefined for systems of units (i.e. SI) in V1, and it was too expensive from the definition and standardization point of view.
I believe we are in agreement. The example I showed for isq::moment_of_force
does not reference any units, just types/specs; one of them was the isq::force
, the other one was the base spec for the dimension of length. We may of course define a isq::length
for that purpose, but eventually, it will have to refer to the SI dimension anyway - we want to restrict the applicable SI units to match the dimension, so we need to know why.
For example, while the diameter of a cylindrical rod can be compared to the height of a block, the diameter of a rod cannot be compared to the mass of a block.
Yeah, from examples like these, (or wavelength and diameter), I gather that ISO 80000 indicates that there are not too many kinds in actual use beyond the base dimensions. To me, that is not necessarily unreasonable.
Yeah, from examples like these, (or wavelength and diameter), I gather that ISO 80000 indicates that there are not too many kinds in actual use beyond the base dimensions. To me, that is not necessarily unreasonable.
Energy does have many, as I alluded to at https://github.com/mpusz/units/issues/405#issuecomment-1372290755. Most *_energy
quantities from the CE link I posted are like that.
Remember that radius is half of a diameter.
The radius of an object is half the diameter of that same object. That doesn't mean that comparing this object's radius to something else always has to give the same result as comparing it's diameter to that same third thing. Assume you have a publication that uses mathematical notation, and describes an object A, whose radius $r$ is 2 m. It also describes an object B, whose diameter $d$ is 3 m. Would you consider $r > d$ to be true? I certainly wouldn't. So radius(1, m) == diameter(2, m)
should either be false
or should not compile.
Also remember that there's a relation between comparison and conversion required by the
std::regular
concept. And that cross-type comparisons require astd::common_reference_t
to modelstd::equality_comparable_with
.
This is not an issue with the hierarchical behaviour I have described; comparisons behave like additions regarding dimensional analysis and compatibility. We already have rules how additions behave, and those explicitly mention a common type.
Yeah, from examples like these, (or wavelength and diameter), I gather that ISO 80000 indicates that there are not too many kinds in actual use beyond the base dimensions. To me, that is not necessarily unreasonable.
Energy does have many, as I alluded to at https://github.com/mpusz/units/issues/405#issuecomment-1372290755. Most *_energy quantities from the CE link I posted are like that.
That is fine with me. All I'm suggesting is that we should try to model kinds so they match general use. The better the references, the easier it is for us to explain that behaviour.
If nothing else, it allows expression of intent.
Yes, but it occupies an identifier and does not add any type safety. For example, right now, I have isq::altitude
and isq::distance
that occupy the identifiers that were much stronger types in the glide_computer of V1. Right now, I should probably even allow them to compare and add in V2.
you could accidentally mix those without taking into account the factor of 2∏.
Well, I still can mix them as they are interconvertible :-(
what of the following should be true?
I had some strong opinions about that before but now I really do not know how to progress here 😢
but eventually, it will have to refer to the SI dimension anyway
This is exactly my point. There is no such thing as SI dimension. SI (being a system of units and not quantities) specifies units for ISQ quantities. Dimension is a property of a quantity, not a unit. Unit can only be defined for a specific quantity/dimension, and this is why in V2, base units take base quantities so I can check in isq::energy[J]
if the dimension/quantity of isq::energy
and J
are compatible. To make it clear I would reword the above statement in the following way "but eventually, it will have to refer to the ISQ quantity type/spec corresponding to this SI unit anyway"
Energy does have many
I am not so sure about it. ISO 80000 never stated it explicitly, and if it did, I suppose it would say that all *_energy
quantities are of kind energy
like it does for length
.
The more we dig into this subject, I feel that we learn that we can do less and less here in terms of C++ type safety. On the one hand, it heavily simplifies the entire library. On the other hand, it removes all of the things that we hoped to be able to model, which is quite frustrating 😢
There is no such thing as SI dimension. SI (being a system of units and not quantities) specifies units for ISQ quantities. Dimension is a property of a quantity, not a unit.
I wasn't aware of that, but you are right of course. In some way, that makes the design even more elegant, as base units are specified in terms of base quantities, and only_for<...>
units become defined in terms of narrower quantities. The distinction between a unit
and a reference
is now almust just a matter of definition: Units tie a name to a measure and a basic quantity. If you want to tie a measure to a narrower quantity, and there is no standardised name for it, it is a reference.
On the other hand, it removes all of the things that we hoped to be able to model, which is quite frustrating 😢.
I believe the machinery we are discussing can model this. It may not match common use of ISQ quantities, where many lengths are comparable, but the user of the library may take those exact same concepts from the standard, and define "stronger" quantities that are not comparable. That is, isq::altitude
and isq::distance
may be comparable, but glide_computer::altitude
and glide_computer::distance
may not (simply define them as "incompatible" narrowing of the previous two). Does that cheer you up a little 😉?
I think I see a reasonable solution providing support for that and most parts of #405 as well. Unfortunately, right now, I provide a week-long training for a customer, so I have limited bandwidth, and after that, I have another week of family vacation. I will be working on it in the background and checking if my assumptions are correct.
@burnpanck, please check https://github.com/mpusz/units/discussions/426 if that is what you meant? Also, I need help with #427.
Done in V2.
Today, after a code review comment, I added a second syntax that creates a quantity in V2 design:
You can find diff with more examples here: https://github.com/mpusz/units/pull/391/commits/858cbb472fe320fdb648fb976f5f026145ba8009.
For now, we have two of those, but I would like to decide which one is better ASAP and remove the other one. Please review the above commit and share your opinion.