Jump to page: 1 2 3
Thread overview
Do you like bounded integrals?
Aug 23, 2016
Cauterite
Aug 23, 2016
Lodovico Giaretta
Aug 24, 2016
Lodovico Giaretta
Aug 23, 2016
Meta
Aug 24, 2016
Nordlöw
Aug 24, 2016
Nordlöw
Aug 25, 2016
Bill Hicks
Aug 29, 2016
Marco Leise
Aug 29, 2016
tsbockman
Aug 30, 2016
Chris Wright
Aug 30, 2016
tsbockman
Aug 30, 2016
Chris Wright
Aug 30, 2016
tsbockman
Aug 30, 2016
tsbockman
August 23, 2016
Currently checkedint (https://github.com/dlang/phobos/pull/4613) stands at 2432 lines and implements a variety of checking behaviors. At this point I just figured I can very easily add custom bounds, e.g. an int limited to 0 through 100 etc. It would take just a few lines because a lot of support is there (bounds hooks, custom min/max) anyway.

However, I fear it might complicate definition and just be a bit much. Here's the design I'm thinking of. Current:

struct Checkedint(T, Hook = Abort);

Under consideration:

struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max);

It's easy to take the limits into account, but then there are a few messes to mind:

* When assigning a Checked to another, should the limits be matched statically or checked dynamically?

* When composing, do the limits compose meaningfully?

* How to negotiate when both the user of Checked and the Hook need to customize the limits? (e.g. if you look at WithNaN it needs to reserve a special value, thus limiting the representable range).

I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition?


Andrei
August 23, 2016
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote:
> I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition?

Well, this occurs very frequently in my code:

struct S {
    int foo;
    invariant {
        assert(ordered_(MIN, foo, MAX));
    };
};

If bounded integers can handle this for me then you've got my support. Assuming the checks disappear in -release.
August 23, 2016
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote:
> * When assigning a Checked to another, should the limits be matched statically or checked dynamically?

The ideal would be implicit cast allowed when widening, explicit required when narrowing. As I think we will have to settle for always explicit, I would say dynamic check. This of course opens the way to many customizations: an user may want to customize what happens when the range check fails. Another user may even want a switch to statically disallow narrowing conversions.

> * When composing, do the limits compose meaningfully?

Every layer should build on the limits exposed by the underlying layer, so I don't see what may go wrong.

> * How to negotiate when both the user of Checked and the Hook need to customize the limits? (e.g. if you look at WithNaN it needs to reserve a special value, thus limiting the representable range).

The idea is that WithNaN will not see the limits of the underlying types, but the limits set by the user. How to do this, see below.

> I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition?

From what I can see in my experience, saturation with custom min/max pops up once in a while in projects. So it's nice to have, even if it is a slight complication in the definition.

> Under consideration:
>
> struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max);

Can I suggest a different approach? Different bounds implemented as a hook?

alias MyBoundedInt = CheckedInt!(int, WithBounds!(0, 42));

The WithBounds hook would only define min, max and opCast. It may have other optional parameters, like whether to statically disallow narrowing casts, or what to do if a narrowing cast is found impossible at runtime.

What do you think about this?
August 23, 2016
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote:
> * When composing, do the limits compose meaningfully?

Just to make sure I understand what you mean by "composing limits", do you mean this?

alias NonNegativeInt = CheckedInt!(int, Abort, 0, int.max);
alias Ubyte = CheckedInt!(NonNegativeInt, Abort, NonNegativeInt(0), NonNegativeInt(255));
Ubyte b; //b is guaranteed to be in [0, 255]

And then we should expect this to fail:

alias Byte = CheckedInt!(NonNegativeInt, Abort, NonNegativeInt(-128), NonNegativeInt(127));


August 23, 2016
On 08/23/2016 05:08 PM, Lodovico Giaretta wrote:
> On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote:
>> * When assigning a Checked to another, should the limits be matched
>> statically or checked dynamically?
>
> The ideal would be implicit cast allowed when widening, explicit
> required when narrowing. As I think we will have to settle for always
> explicit, I would say dynamic check. This of course opens the way to
> many customizations: an user may want to customize what happens when the
> range check fails. Another user may even want a switch to statically
> disallow narrowing conversions.
>
>> * When composing, do the limits compose meaningfully?
>
> Every layer should build on the limits exposed by the underlying layer,
> so I don't see what may go wrong.

That wouldn't work for e.g. NaN. A NaN wants to "steal" a value but only if that's not available. It's complicated.

>> * How to negotiate when both the user of Checked and the Hook need to
>> customize the limits? (e.g. if you look at WithNaN it needs to reserve
>> a special value, thus limiting the representable range).
>
> The idea is that WithNaN will not see the limits of the underlying
> types, but the limits set by the user. How to do this, see below.

Nonono, NaN should be able to see the underlying type.

>> I think all of these questions have answers, but I wanted to gauge the
>> interest in bounded checked integrals. Would the need for them justify
>> additional complications in the definition?
>
> From what I can see in my experience, saturation with custom min/max
> pops up once in a while in projects. So it's nice to have, even if it is
> a slight complication in the definition.
>
>> Under consideration:
>>
>> struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max);
>
> Can I suggest a different approach? Different bounds implemented as a hook?
>
> alias MyBoundedInt = CheckedInt!(int, WithBounds!(0, 42));
>
> The WithBounds hook would only define min, max and opCast. It may have
> other optional parameters, like whether to statically disallow narrowing
> casts, or what to do if a narrowing cast is found impossible at runtime.
>
> What do you think about this?

It's the first thing I tried and it doesn't do well. The first thing ou want with a bounded type is to combine it with any other policy (abort, assert, saturate...). This kind of horizontal communication between hooks is tenuous and not supported well by the design.


Andrei

August 24, 2016
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote:
> Currently checkedint (https://github.com/dlang/phobos/pull/4613) stands at 2432 lines and implements a variety of checking behaviors. At this point I just figured I can very easily add custom bounds, e.g. an int limited to 0 through 100 etc. It would take just a few lines because a lot of support is there (bounds hooks, custom min/max) anyway.
>
> struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max);

For comparion take a look at my solution at:

https://github.com/nordlow/phobos-next/blob/master/src/bound.d

It may answer some of your questions.

The CT-param `exceptional` should be related to your `Hook`.

The solution is currently bloated as it is interleaved with an Optional implementation and packing logic.

The CT-params `optional`, `exceptional`, `packed` and `signed` should probably be merged and stored in a Flags-type.

> * When assigning a Checked to another, should the limits be matched statically or checked dynamically?

I'm not sure. I believe the answer lies in the semantic (real-life) interpretations of the boundeed types. If the ranges are related to physical boundaries it should probably be checked at compile-time like we do with units of measurement libraries.

> * When composing, do the limits compose meaningfully?

What do you mean with composing? If you're talking about value-range algebra then take a look at the definitions of `opBinary`, `min`, `max` and `abs` in my `bound.d`.

> I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition?

One other additional use is for Ada-style fixed-length-array index types. Such a type can be both bounds-checked and optionally tied to a specific subtype of a fixed-length array. Such an implementation is `IndexedBy` at

https://github.com/nordlow/phobos-next/blob/master/src/typecons_ex.d#L167

which is currently in use in my trie-container

https://github.com/nordlow/phobos-next/blob/master/src/trie.d
August 24, 2016
On Wednesday, 24 August 2016 at 08:39:28 UTC, Nordlöw wrote:
> Such an implementation is `IndexedBy` at
>
> https://github.com/nordlow/phobos-next/blob/master/src/typecons_ex.d#L167

Correction: Index type here is actually a Ada-style modulo type which does wrap-around instead of truncation.
August 24, 2016
On Wednesday, 24 August 2016 at 00:40:15 UTC, Andrei Alexandrescu wrote:
> That wouldn't work for e.g. NaN. A NaN wants to "steal" a value but only if that's not available. It's complicated.

Ok, I get your point. WithNaN should not waste the "official range" when there is a huge wasted representable range that can be exploited to choose the special NaN value. While this would be a very good thing, it poses a problem.

If a hook is allowed to special-case values outside the official range, then it may happen that composing two hooks, they both special-case the same value, leading to wrong results.
For example, in some statistical oriented environments, the special value NA (not available), very similar to NaN, is used to represent missing input data; a WithNA hook may decide to use the same policy used by WithNaN to reserve its NA value, causing corruption, as the same value has two meanings.

So, the first solution is that hooks should always reserve special values from the edges of the official range, and expose to the higher layer a correctly reduced official range.

The second solution is having two ranges: the "official range" and the "usable range", where the usable range is the full representable range minus the special values already used. Hooks must take special values from the usable range and reduce it accordingly, while leaving the official range intact.

The third (more complex) solution, which leaves the official range intact, is that hooks should take special values from wherever outside the official range and in some way communicate to the higher level which values are already taken. This is not impossible nor conceptually difficult, but it may not be worth.
August 24, 2016
On 08/24/2016 05:05 AM, Lodovico Giaretta wrote:
> On Wednesday, 24 August 2016 at 00:40:15 UTC, Andrei Alexandrescu wrote:
>> That wouldn't work for e.g. NaN. A NaN wants to "steal" a value but
>> only if that's not available. It's complicated.
>
> Ok, I get your point. WithNaN should not waste the "official range" when
> there is a huge wasted representable range that can be exploited to
> choose the special NaN value.

Yes, thanks for divining the meaning from the poor explanation.

> While this would be a very good thing, it
> poses a problem.
>
> If a hook is allowed to special-case values outside the official range,
> then it may happen that composing two hooks, they both special-case the
> same value, leading to wrong results.
> For example, in some statistical oriented environments, the special
> value NA (not available), very similar to NaN, is used to represent
> missing input data; a WithNA hook may decide to use the same policy used
> by WithNaN to reserve its NA value, causing corruption, as the same
> value has two meanings.

Yes, that is indeed a potential problem.

> So, the first solution is that hooks should always reserve special
> values from the edges of the official range, and expose to the higher
> layer a correctly reduced official range.
>
> The second solution is having two ranges: the "official range" and the
> "usable range", where the usable range is the full representable range
> minus the special values already used. Hooks must take special values
> from the usable range and reduce it accordingly, while leaving the
> official range intact.
>
> The third (more complex) solution, which leaves the official range
> intact, is that hooks should take special values from wherever outside
> the official range and in some way communicate to the higher level which
> values are already taken. This is not impossible nor conceptually
> difficult, but it may not be worth.

The fourth solution is to document hooks appropriately and acknowledge the fact (which is already true) that not all hooks can work together.


Andrei

August 24, 2016
what about two types.

alias BI = Bound!int;

alias CBI = CheckedInt!BI;

if Bound behaves as an integer people can choose.
« First   ‹ Prev
1 2 3