Date: Thu, 27 Jun 2024 20:52:20 -0400
On Thu, Jun 27, 2024 at 6:59 PM Tiago Freire <tmiguelf_at_[hidden]> wrote:
> > Unit safety, as defined here, is a property that any individual line of
> code does or does not have. You're absolutely right that no API could ever
> achieve it universally. It's always possible for a sufficiently
> adversarial user to defeat it. The goal, though, is to design APIs that
> make unit safety easy to achieve, by strong default.
>
>
>
> I don’t get it!
>
> It looks very selective what you consider an “adversarial” example.
>
> I have my type explicitly written out, I know what I want, its “40 of this
> thing”, but you force me to use 3 different types and 2 or more different
> objects multiplying together to create an object of an explicit type.
>
> I never heard std::chrono::duration to be described as unsafe… in order to
> declare it you need to write a type or pass it too a thing that has a type,
> am I the only one that looks at the type of things?
>
The chrono library was a huge leap forward in usability, but we've also
learned a lot in the decade-plus since then. And the `.count()` member is
an excellent example of an interface that is *not* unit-safe. You can
change the underlying duration type, and the `.count()` return value will
change accordingly, silently breaking code that made an unverified
assumption as to the period of that duration. And this is more than merely
theoretical: I believe the units for the system_clock duration are
different among the three major compilers, being (I think) ns on gcc, us on
clang, and (100 * ns) (!) on MSVC. Thus, if you write code that gets a
system_clock duration, and calls `.count()`, it will break on a different
compiler --- because `.count()` is not unit-safe.
> I’m not of the opinion that forcing developers to write extra appendages
> makes it safer as opposed to making developers more frustrated.
>
Fascinating! I hate to be the bearer of bad news, but you will not
*believe* what Some Other Person has been posting under a name with the
same spelling as yours. For example, here's how they proposed to convert a
temperature in Fahrenheit to one in Celsius:
units::celsius temperature_celcius{1.5};
units::farenheit temperature_farenheit = units::farenheit{
temperature_celcius.to<units::kelvin>().to<units::rankine>()};
I'm not kidding --- they actually posted this as the preferred method for
the simple operation of converting Fahrenheit to Celsius! I thought this
must have been a mistake, but they confirmed it was a deliberate design
decision.
> Even recently a quite significant change was forced upon it because of how
> blatantly unsafe it was when dealing with temperatures.
>
> It was not the first time it was brought up, but it was simply ignored
> until I had shown a quite common example, publicly, and someone else other
> than myself started to complain “this is not safe at all”.
>
>
>
> And the “solution” was to make the design even more complicated, forcing
> the user to write even more appendages, requiring more to learn to even use.
>
> And the problem isn’t fixed by the way, sure it forces you to read that
> extra text while instantiating the object directly, but there are still
> many other ways to do it indirectly, and I have shown an example on how one
> could do it in code that could be reasonably written without thinking
> “adversarially” (perhaps you might as well just rename the whole thing as
> quantity_delta).
>
> Yet refuse to take what I would argue is the easiest decision that would
> immediately improve safety… there is no such thing as delta temperatures in
> degrees Celsius...
>
> You are not using the type system to stop users from doing the wrong
> thing, you are using the type system to force people to write documentation
> and hopefully they will read it and figure out that they are doing the
> wrong thing.
>
>
>
>
>
> I’m really not trying to beat up a dead horse here… but It doesn’t need to
> be like this.
>
> You are struggling because the system is over complicated, and you don’t
> understand where the complexity comes from.
>
>
>
>
>
>
>
> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]> *On Behalf
> Of *Charles R Hogg via Std-Proposals
> *Sent:* Thursday, June 27, 2024 23:10
> *To:* std-proposals_at_[hidden]
> *Cc:* Charles R Hogg <charles.r.hogg_at_[hidden]>
> *Subject:* Re: [std-proposals] On the standardization of mp-units P3045R1
>
>
>
>
>
>
>
> On Thu, Jun 27, 2024 at 4:57 PM Lorand Szollosi via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> Hi,
>
>
>
> On Thu, Jun 27, 2024 at 10:03 PM Charles R Hogg via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> The way to achieve this with a units library is to make sure that every
> interface that *brings values into* a units library type, or *extracts
> values from* a units library type, forces the user to *name the unit* explicitly,
> *at the callsite*. Au was designed with this approach in mind from the
> ground up, and I was very heartened to see mp-units adopt it as well, to
> the point that mp-units is now every bit the equal of Au in unit safety.
> (Really, the whole 2.0 redesign for mp-units was a stunning leap forward in
> a lot of ways --- I was thrilled to see it.)
>
>
>
> What you call unit safety, I'd argue, is a coding convention. You might
> enforce it by power; you can't enforce it solely by c++ language features, *and
> that's a good thing*. It's because it goes against composability. Think
> about this code:
>
>
>
> auto l = [](double d) constexpr { return d * km; };
>
> static_assert(l(12) == 12 * km);
>
>
>
> Full example: https://godbolt.org/z/PWocWYo6c
>
>
>
> I'd argue that l(12) is *not* unit-safe: simply reading that line, you
> cannot tell whether that static assert should succeed (i.e., I wrote d * km
> in the lambda) or fail (i.e., if I were to write d * m, or even 2 * d *
> km). I also didn't write return type in the lambda, as it's auto. Thus, no
> matter what you ensure in the library, if a user would like a lambda
> constructing a unit from a double, *they have it today*. Therefore, the
> argument is, whether it's a *coding standard* we promote.
>
>
>
> Unit safety, as defined here, is a property that any individual line of
> code does or does not have. You're absolutely right that no API could ever
> achieve it universally. It's always possible for a sufficiently
> adversarial user to defeat it. The goal, though, is to design APIs that
> make unit safety easy to achieve, by strong default.
>
>
>
> In this specific example, I fully agree that l(12) is not unit-safe. In
> fact, the *root* of this problem is that "auto l = ..." is not
> unit-safe. This *individual line* takes a raw number, and returns a
> quantity, but the name `l` *does not name* the unit. Therefore, *this
> individual line* should fail code review because it makes an *interface* that
> is not unit-safe, and that will cause downstream users to write
> non-unit-safe code.
>
>
>
>
>
> Now to your question: if you have a typename that contains the unit name,
> isn't that enough? Say we had a *type* called `degrees_celsius`: why
> wouldn't it be OK to have a constructor from `double`, so you could simply
> write `degrees_celsius{42.0}`? (We would make it `explicit`, of course!)
>
>
>
> There are units that you cannot construct as a + b * x, where an and b are
> constants. A common example is date (based on some absolute time) of
> countries: there are examples of days missing from calendars. Thus, in your
> serialisation / deserialisation, you'll need a way to express
> quantity_point constructions anyways. Users of the library could create
> such a thing in non-standard ways; or, we can provide a standard function
> to create it.
>
>
>
> I believe dates generally are out of scope for P3045. That's more the
> domain of the chrono library. (Of course, we'll want robust interop
> between chrono *durations* and *durations* in the standard units
> library. Thankfully, the existing open-source libraries on which the
> proposal is based already have this property.)
>
>
>
> Cheers,
>
> Chip
>
>
>
>
>
> Units are still checked while compiling, so zero type safety is sacrificed.
>
>
>
> Thanks,
>
> -lorro
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
> > Unit safety, as defined here, is a property that any individual line of
> code does or does not have. You're absolutely right that no API could ever
> achieve it universally. It's always possible for a sufficiently
> adversarial user to defeat it. The goal, though, is to design APIs that
> make unit safety easy to achieve, by strong default.
>
>
>
> I don’t get it!
>
> It looks very selective what you consider an “adversarial” example.
>
> I have my type explicitly written out, I know what I want, its “40 of this
> thing”, but you force me to use 3 different types and 2 or more different
> objects multiplying together to create an object of an explicit type.
>
> I never heard std::chrono::duration to be described as unsafe… in order to
> declare it you need to write a type or pass it too a thing that has a type,
> am I the only one that looks at the type of things?
>
The chrono library was a huge leap forward in usability, but we've also
learned a lot in the decade-plus since then. And the `.count()` member is
an excellent example of an interface that is *not* unit-safe. You can
change the underlying duration type, and the `.count()` return value will
change accordingly, silently breaking code that made an unverified
assumption as to the period of that duration. And this is more than merely
theoretical: I believe the units for the system_clock duration are
different among the three major compilers, being (I think) ns on gcc, us on
clang, and (100 * ns) (!) on MSVC. Thus, if you write code that gets a
system_clock duration, and calls `.count()`, it will break on a different
compiler --- because `.count()` is not unit-safe.
> I’m not of the opinion that forcing developers to write extra appendages
> makes it safer as opposed to making developers more frustrated.
>
Fascinating! I hate to be the bearer of bad news, but you will not
*believe* what Some Other Person has been posting under a name with the
same spelling as yours. For example, here's how they proposed to convert a
temperature in Fahrenheit to one in Celsius:
units::celsius temperature_celcius{1.5};
units::farenheit temperature_farenheit = units::farenheit{
temperature_celcius.to<units::kelvin>().to<units::rankine>()};
I'm not kidding --- they actually posted this as the preferred method for
the simple operation of converting Fahrenheit to Celsius! I thought this
must have been a mistake, but they confirmed it was a deliberate design
decision.
> Even recently a quite significant change was forced upon it because of how
> blatantly unsafe it was when dealing with temperatures.
>
> It was not the first time it was brought up, but it was simply ignored
> until I had shown a quite common example, publicly, and someone else other
> than myself started to complain “this is not safe at all”.
>
>
>
> And the “solution” was to make the design even more complicated, forcing
> the user to write even more appendages, requiring more to learn to even use.
>
> And the problem isn’t fixed by the way, sure it forces you to read that
> extra text while instantiating the object directly, but there are still
> many other ways to do it indirectly, and I have shown an example on how one
> could do it in code that could be reasonably written without thinking
> “adversarially” (perhaps you might as well just rename the whole thing as
> quantity_delta).
>
> Yet refuse to take what I would argue is the easiest decision that would
> immediately improve safety… there is no such thing as delta temperatures in
> degrees Celsius...
>
> You are not using the type system to stop users from doing the wrong
> thing, you are using the type system to force people to write documentation
> and hopefully they will read it and figure out that they are doing the
> wrong thing.
>
>
>
>
>
> I’m really not trying to beat up a dead horse here… but It doesn’t need to
> be like this.
>
> You are struggling because the system is over complicated, and you don’t
> understand where the complexity comes from.
>
>
>
>
>
>
>
> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]> *On Behalf
> Of *Charles R Hogg via Std-Proposals
> *Sent:* Thursday, June 27, 2024 23:10
> *To:* std-proposals_at_[hidden]
> *Cc:* Charles R Hogg <charles.r.hogg_at_[hidden]>
> *Subject:* Re: [std-proposals] On the standardization of mp-units P3045R1
>
>
>
>
>
>
>
> On Thu, Jun 27, 2024 at 4:57 PM Lorand Szollosi via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> Hi,
>
>
>
> On Thu, Jun 27, 2024 at 10:03 PM Charles R Hogg via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> The way to achieve this with a units library is to make sure that every
> interface that *brings values into* a units library type, or *extracts
> values from* a units library type, forces the user to *name the unit* explicitly,
> *at the callsite*. Au was designed with this approach in mind from the
> ground up, and I was very heartened to see mp-units adopt it as well, to
> the point that mp-units is now every bit the equal of Au in unit safety.
> (Really, the whole 2.0 redesign for mp-units was a stunning leap forward in
> a lot of ways --- I was thrilled to see it.)
>
>
>
> What you call unit safety, I'd argue, is a coding convention. You might
> enforce it by power; you can't enforce it solely by c++ language features, *and
> that's a good thing*. It's because it goes against composability. Think
> about this code:
>
>
>
> auto l = [](double d) constexpr { return d * km; };
>
> static_assert(l(12) == 12 * km);
>
>
>
> Full example: https://godbolt.org/z/PWocWYo6c
>
>
>
> I'd argue that l(12) is *not* unit-safe: simply reading that line, you
> cannot tell whether that static assert should succeed (i.e., I wrote d * km
> in the lambda) or fail (i.e., if I were to write d * m, or even 2 * d *
> km). I also didn't write return type in the lambda, as it's auto. Thus, no
> matter what you ensure in the library, if a user would like a lambda
> constructing a unit from a double, *they have it today*. Therefore, the
> argument is, whether it's a *coding standard* we promote.
>
>
>
> Unit safety, as defined here, is a property that any individual line of
> code does or does not have. You're absolutely right that no API could ever
> achieve it universally. It's always possible for a sufficiently
> adversarial user to defeat it. The goal, though, is to design APIs that
> make unit safety easy to achieve, by strong default.
>
>
>
> In this specific example, I fully agree that l(12) is not unit-safe. In
> fact, the *root* of this problem is that "auto l = ..." is not
> unit-safe. This *individual line* takes a raw number, and returns a
> quantity, but the name `l` *does not name* the unit. Therefore, *this
> individual line* should fail code review because it makes an *interface* that
> is not unit-safe, and that will cause downstream users to write
> non-unit-safe code.
>
>
>
>
>
> Now to your question: if you have a typename that contains the unit name,
> isn't that enough? Say we had a *type* called `degrees_celsius`: why
> wouldn't it be OK to have a constructor from `double`, so you could simply
> write `degrees_celsius{42.0}`? (We would make it `explicit`, of course!)
>
>
>
> There are units that you cannot construct as a + b * x, where an and b are
> constants. A common example is date (based on some absolute time) of
> countries: there are examples of days missing from calendars. Thus, in your
> serialisation / deserialisation, you'll need a way to express
> quantity_point constructions anyways. Users of the library could create
> such a thing in non-standard ways; or, we can provide a standard function
> to create it.
>
>
>
> I believe dates generally are out of scope for P3045. That's more the
> domain of the chrono library. (Of course, we'll want robust interop
> between chrono *durations* and *durations* in the standard units
> library. Thankfully, the existing open-source libraries on which the
> proposal is based already have this property.)
>
>
>
> Cheers,
>
> Chip
>
>
>
>
>
> Units are still checked while compiling, so zero type safety is sacrificed.
>
>
>
> Thanks,
>
> -lorro
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
Received on 2024-06-28 00:52:35