Replies: 12 comments 59 replies
-
This is an interesting discussion.
I have thought about the general issue in the past, but not to the extent in this discussion.
To compute averages, you need addition. This is similar: inline constexpr struct no_money : absolute_point_origin<currency> {} no_money; It makes me think "duh", which isn't good.
Shouldn't this be
The ISO 80000 definition of distance screams relative.
The ISO 80000 definition of altitude definitely suggests a point.
What if you could write something like
What lengths, though?
I think it was in #93 where I suggested different names that don't favor any. |
Beta Was this translation helpful? Give feedback.
-
If we do this, I expect it moves standardization to C++32, minimum: it's such a drastic change that we'll be starting from scratch on implementation experience. What's more, I think there's an ~80% chance that what we'd be standardizing in C++32 would be very similar to what we have today: I don't think this change is an improvement, and I expect the extra time would go into discovering precisely why. It's not at all clear to me that a speed is well modeled by a quantity point. I definitely see the motivation --- in fact, I've explored similar ideas myself! But quantity points don't support multiplication, so we would be destroying the ability to multiply a speed by a time and get a displacement. And ultimately, that's what a speed is --- a ratio of two quantities, one of distance and one of time. Big picture: I think what we are bringing to the committee is already very good! There will always be the temptation to polish and refine; it will never feel "done". But we need to be especially wary of making changes that unduly increase friction for more casual users, changes that raise the barrier for entry. mp-units has done a great job making its more sophisticated features, like quantity kinds, get out of the way for people who don't care to use them. Let's stick with this approach so that we end up with a library that far more people will actually use. And if it does turn out that points are "better", but we stick with the status quo? The "failure mode" is that we have taken probably the best software units library yet written (every library is quantity-default!) and provided it to the entire C++ community. I can live with that. |
Beta Was this translation helpful? Give feedback.
-
Please let me know if you would like to see any of the changes described above being implemented. |
Beta Was this translation helpful? Give feedback.
-
Finally, I had some time to work on it, and I really like the results. The previous solution to the Forbes issue was the following: {
std::cout << "Input in degree Celsius\n";
const quantity_point max = si::ice_point + 43. * deg_C;
const quantity_point avg = si::ice_point + 25. * deg_C;
std::cout << "max: " << max.quantity_from(si::ice_point) << " ("
<< max.quantity_from(usc::zero_Fahrenheit).in(deg_F) << ")\n";
std::cout << "avg: " << avg.quantity_from(si::ice_point) << " ("
<< avg.quantity_from(usc::zero_Fahrenheit).in(deg_F) << ")\n";
}
{
std::cout << "\nInput in degree Fahrenheit\n";
const quantity_point max = usc::zero_Fahrenheit + 109. * deg_F;
const quantity_point avg = usc::zero_Fahrenheit + 77. * deg_F;
std::cout << "max: " << max.quantity_from(usc::zero_Fahrenheit) << " ("
<< max.quantity_from(si::ice_point).in(deg_C) << ")\n";
std::cout << "avg: " << avg.quantity_from(usc::zero_Fahrenheit) << " ("
<< avg.quantity_from(si::ice_point).in(deg_C) << ")\n";
} With the changes I did today on my PC, now we can write: {
std::cout << "Input in degree Celsius\n";
const quantity_point max = 43. * deg_C;
const quantity_point avg = 25. * deg_C;
std::cout << "max: " << max.quantity_from_zero() << " (" << max.in(deg_F).quantity_from_zero() << ")\n";
std::cout << "avg: " << avg.quantity_from_zero() << " (" << avg.in(deg_F).quantity_from_zero() << ")\n";
}
{
std::cout << "\nInput in degree Fahrenheit\n";
const quantity_point max = 109. * deg_F;
const quantity_point avg = 77. * deg_F;
std::cout << "max: " << max.quantity_from_zero() << " (" << max.in(deg_C).quantity_from_zero() << ")\n";
std::cout << "avg: " << avg.quantity_from_zero() << " (" << avg.in(deg_C).quantity_from_zero() << ")\n";
} How do you like the above?
BTW, this |
Beta Was this translation helpful? Give feedback.
-
Implicit point origin for units that do not predefine one has some issues that we must understand. For example: EXPLICIT ORIGINSconstexpr struct mean_sea_level : absolute_point_origin<mean_sea_level, isq::altitude> {} mean_sea_level;
constexpr struct home : absolute_point_origin<home, isq::distance> {} home;
// quantity_point<si::metre, mean_sea_level> alt = 42 * m; // does not compile
// quantity_point<si::kilo<si::metre>, home> airport = 10 * km; // does not compile
quantity_point<si::metre, mean_sea_level> alt = mean_sea_level + 42 * m;
quantity_point<si::kilo<si::metre>, home> airport = home + 10 * km;
std::cout << alt.quantity_from(mean_sea_level) << "\n"; // 42 m
std::cout << airport.quantity_from(home) << "\n"; // 10 km
// std::cout << alt.quantity_from_zero() << "\n"; // does not compile
// std::cout << airport.quantity_from_zero() << "\n"; // does not compile
// quantity q = alt - airport; // does not compile Should IMPLICIT POINT ORIGINS FOR UNITS-ONLY MODEquantity_point<si::metre> alt = 42 * m;
quantity_point<si::kilo<si::metre>> airport = 10 * km;
// quantity_point<si::metre> alt = mean_sea_level + 42 * m; // does not compile
// quantity_point<si::kilo<si::metre>> airport = home + 10 * km; // does not compile
// std::cout << alt.quantity_from(mean_sea_level) << "\n"; // does not compile
// std::cout << airport.quantity_from(home) << "\n"; // does not compile
std::cout << alt.quantity_from_zero() << "\n"; // 42 m
std::cout << airport.quantity_from_zero() << "\n"; // 10 km
quantity q = alt - airport;
std::cout << q << "\n"; // -9958 m IMPLICIT POINT ORIGINS FOR TYPED QUANTITIESquantity_point<isq::altitude[si::metre]> alt = 42 * m;
quantity_point<isq::distance[si::kilo<si::metre>]> airport = 10 * km;
// quantity_point<isq::altitude[si::metre]> alt = mean_sea_level + 42 * m; // does not compile
// quantity_point<isq::distance[si::kilo<si::metre>]> airport = home + 10 * km; // does not compile
// std::cout << alt.quantity_from(mean_sea_level) << "\n"; // does not compile
// std::cout << airport.quantity_from(home) << "\n"; // does not compile
std::cout << alt.quantity_from_zero() << "\n"; // 42 m
std::cout << airport.quantity_from_zero() << "\n"; // 10 km
// quantity q = alt - airport; // does not compile
This raises the question if |
Beta Was this translation helpful? Give feedback.
-
Here's an interesting tangent that occurred to me, so I'm breaking it off into a separate thread. How do quantity points behave when you have two different origins that have no defined relationship at all to each other? I assume that it would be impossible to convert back and forth between them. If so, that could be really useful! Here's a use case. Imagine we have a unit/integration testing framework for self-driving, which has a small subset of a road network. We would like to use quantity points to label the positions along the road --- think of them as analogous to "mile markers". But there's a problem when we have two intersecting roads. Each should be labeled with quantity points, but we definitely don't want to mix up points from one road's "frame" with points from the other's! Maybe we can have two origins, |
Beta Was this translation helpful? Give feedback.
-
Gašper Ažman on |
Beta Was this translation helpful? Give feedback.
-
So, the main issue with prices being *points* is that you shouldn't be
adding them, because adding two prices results in a "price for a bundle" as
opposed to "price per unit". In other words, I've seen bugs.
Basically, price_per_unit * quantity = price_you_pay, but price_per_unit +
price_per_unit makes no sense (unless you're doing an interpolation and
dividing by number of units right after, which is an operation that *is*
defined on point-spaces).
so, price_you_pay is a vector space, price_per_unit is a point-space. Still
disagree?
…On Mon, Jan 8, 2024 at 3:54 PM Chip Hogg ***@***.***> wrote:
I was going to write back, but I think I could hardly have expressed
myself better than @burnpanck <https://github.com/burnpanck>'s comment
above.
My point was going to be that a key property of point types is that the
values are *labels*, and the point we decide to label as "zero" is
arbitrary. If anyone thinks this is true of prices, then I have a bridge to
sell them for a price of "zero". 😉
I was also going to explicitly agree that prices *can* be modeled as
points in certain domains. It just seems to me that what we want here is
only a subset of the usual set of "point properties" (such as restricting
addition, again, *in certain domains*), whereas other properties (such as
inability to multiply, or freedom to set the zero point) make no sense.
—
Reply to this email directly, view it on GitHub
<#514 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAA5R5JQ6GFHJLUIZTMI673YNQJCFAVCNFSM6AAAAAA66VNUCKVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DANJRG4ZDS>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Note that price_per_unit * quantity = price you pay, but
Price_per_unit * scaling_factor = price_per_unit
These are not the same.
…On Mon, Jan 8, 2024, 18:43 Chip Hogg ***@***.***> wrote:
I think there are two related but separate questions, which we should
avoid conflating.
First, "is it useful to prevent adding price-per-unit?" If you as the
domain expert say that it is, then I'm more than happy to concede the...
er, point. 😅
Next, "are these point types?" Here, I'm not sold yet. Point types should
not admit any sensible notion of multiplication. But for price-per-unit,
multiplication is well defined and indeed strongly unambiguous. If the
price per unit triples, everyone agrees what that means!
The sense I get from this domain is less one of "point types", and more
one of "types where we want to prevent accidental addition".
—
Reply to this email directly, view it on GitHub
<#514 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAA5R5IKKCKGGXXINVTDRL3YNQ44ZAVCNFSM6AAAAAA66VNUCKVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DANJUGA2TA>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Fundamentally, this is about flexibility in modeling your domain.
We ended up with a fairly complete mixin system for operators on a type,
but that usually just went as far as points-over-module/ring. We then had
macros that defined the rest of the valid operators across types.
We needed to model different kinds of price deltas separately, so different
fees had different types (and could only be applied once), day-start
position was a different type than current day position, etc. This is all
for the same instrument.
This helped onboard people really quickly because it was easy to see where
a newcomer was struggling with the type system, which showed where their
understanding of the business was incomplete.
To derive requirements from the above: every type can be part of multiple
arithmetic structures with outer operations, and making that modeling clear
and simple is the killer feature.
…On Tue, Jan 9, 2024, 09:44 Mateusz Pusz ***@***.***> wrote:
If we model the price as quantity points we immediately gain lots of
safety but also pay with usability issues. Here is a short finance example
we were working on with Ari Jort in Kona: https://godbolt.org/z/6v3zG1dfr.
@atomgalaxy <https://github.com/atomgalaxy>, do you see how we could
improve the library to make such cases more suitable for your domain?
—
Reply to this email directly, view it on GitHub
<#514 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAA5R5KXOQX5T36QEYK7HXTYNUGRPAVCNFSM6AAAAAA66VNUCKVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DANRSHAYDI>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
On Tue, Jan 9, 2024, 01:05 Chip Hogg ***@***.***> wrote:
Also, *totally unrelated*, but since this is the first time we've
interacted --- thank you for all your work on "deducing this"! It's *the*
defining feature of C++23. It's remarkable after all these decades to see a
core language feature which solves so many seemingly unrelated problems. It
fits so neatly that it feels like it simply should have always been there.
Bravo!
Thanks! I'm looking forward to using it myself :). I hope it makes mixins
better and cheaper - and this library will definitely benefit from that.
As for starting a new thread - I'm just hitting reply-all, not sure what's
wrong on GitHub's side.
—
… Reply to this email directly, view it on GitHub
<#514 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAA5R5P3RD5UNBTDHF7DI5LYNSJWHAVCNFSM6AAAAAA66VNUCKVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DANJYG43DM>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Whatever you wanna call it, it's lerp.
Maybe lerp_range(points, weights).
That's the op that's defined on points.
…On Wed, Jan 10, 2024, 10:09 Mateusz Pusz ***@***.***> wrote:
Right, we could provide lerp for quantity_point types to do the
interpolation. However, I think previously you meant a different operation.
Something more like an average (price1 + price2 + price 3 / 3), right? As
points can't be added it is not that easy to actually find a good interface
for such an operation.
—
Reply to this email directly, view it on GitHub
<#514 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAA5R5M6Q5XQLDSGSZIMR23YNZSGHAVCNFSM6AAAAAA66VNUCKVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DANZXHA3TK>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
The more I think about it, the more I am getting convinced that we might have got the defaults wrong. It seems to be a common issue in the C++ language ;-)
People who are not experts in the domain typically think about (and use) quantity points only when talking about temperatures. If they know
std::chrono
, they might also be tempted to use them for time points. That's it. Most users will not use points for anything else.However, it seems that most of the things we want to model are points. The speed of a car, the altitude of a point, some position distant from home, latitude and longitude, heading, energy state, and many more are actually points. If we think about this as being our default, suddenly, the entire discussion we have about the non-negative quantities in #468 starts to make sense. Immediately, a radius can't be negative. Likewise, the speed of a car defined by ISO as a magnitude of the velocity (so implicitly non-negative), also seems to be non-controversial.
Our
quantity
type, being the workhorse of the library, is a relative type, though. We should talk about relative types only when we talk about the difference between two points, so a delta of some quantity. It seems that such use cases are actually less common. Let's think about the interfaces we wrote in the last years for our libraries:However, people tend to use
quantity
for those in the majority of cases. Even we, who are the experts in the domain, do the same. There might be a few reasons for it:quantity
provides all operations ofquantity_point
and more. People often do not think if adding two quantity values for abstractions they want to model makes sense or not. Often, it does not, which is a clear highlight that a point should be used instead. But as it compiles fine, aquantity
type is wrongly used in such cases. Even ouravg_speed
example that we use in every documentation or slides returns a relative type instead of the point.quantity_point
is more to type thanquantity
. There are also more template parameters if we want to provide them explicitly. Also, currently, thequantity_point
construction is harder and more verbose as it always requires the addition/subtraction ofPointOrigin
and aquantity
.Maybe we should change the defaults? Maybe a
quantity
should mean a point, and maybe we should rename the current type to something likedelta_quantity
? If the user would have to type more for those, automatically, a point would become the default. We could consider various names for the differential type:delta_quantity
relative_quantity
diff_quantity
vector_quantity
(this name might collide with vector and tensor quantities in the ISO meaning).With such naming conventions for those two abstractions, people would often initially use points by default and switch to relative types in case of a compilation error (clearly highlighting that a point is not a good abstraction there).
Maybe we should also simplify the usage of points in the library to encourage their wider usage? The biggest issue I see is that, right now, we require a strong type for point origin to be explicitly defined and used when we just want to express the point relative to zero. In the currency example, at some point, I already ended up with something like:
That is quite inconvenient, to say the least. It is even worse when we do not want to, or simply can't, use CTAD. For example:
For most of the quantities, there is no need to redefine a point that just means zero all over again. To simplify the usage of such points, maybe we should provide an implicit zero point origin in the library?
Maybe we should also restore the implicit point origin when the user does not provide one?
With the above, we could type:
Maybe we should again allow the construction of a point just from the relative quantity without the need to explicitly mention the origin?
However, please note that even with the above, the multiply syntax still means the relative type:
The above changes will simplify the usage of most of the points a lot. However, it will break the current verbose but always correct usage of temperatures in units other than kelvins. From the below examples only
t2
andt4
will construct what most users expect:This is really unfortunate :-( I also think that the current approach of requiring the user to always provide
si::zero_Celsius
(often even twice) is a bit unfortunate as well:quantity_point<si:degree_Celsius, si::zero_Celsius> t = si::zero_Celsius + 20 * deg_C;
Also, many users will probably find it hard to define their own first point as they may not be aware of
si::zero_Celsius
(orusc::zero_Fahrenheit
) existence.This is why maybe we should rediscuss providing a default point origin in a unit definition?
With such support, we could redefine the
quantity_point
to first check for a point origin in a unit definition and only otherwise, use the zero point origin:With the above, all the
t1
-t4
cases provided before would yield the expected type and value.One more point to consider here is if a construction of a point from a difference type should be allowed to happen implicitly or should it be explicit only.
To summarize, I have a strong feeling that the changes described above would not only greatly simplify the current usage of points for temperatures but would also encourage users to use points more often for other quantity types, which would improve their designs and safety.
With all of the above changes, our hello units example could be changed to:
The above changes might look a bit drastic at first, but please notice how they:
I am not claiming that we should apply all of those changes. Please treat the above proposals with some grain of salt. Some of them are intentionally a bit controversial to force us to move a bit from the paved paths and explore new possibilities with an open mind. Maybe some of them are reasonable and should be implemented though?
Please do share your thoughts, questions, preferences, and suggestions.
Beta Was this translation helpful? Give feedback.
All reactions