Proposed Changes to the Range API for Phobos v3

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri May 17 06:40:42 UTC 2024


On Thursday, May 16, 2024 10:48:26 PM MDT Richard (Rikki) Andrew Cattermole 
via Digitalmars-d wrote:
> On 17/05/2024 4:22 AM, Jonathan M Davis wrote:
> > It seems like you're proposing that we start requiring wrapper structs for
> > some of the most common kinds of ranges, whereas my proposal would be
> > requiring wrapper structs only for rare ranges - ones which are already
> > typically generated using helper functions.
>
> Required, yes, automatic yes.
>
> The language could help here, not a pleasant proposal but it would be
> do-able to have the language auto-wrap into a boxed type. Given ranges
> are first class in D, it'll be worth it.
>
> ```d
> import core.attributes : autobox;
>
> struct Map(@autobox T) {
>   this(@autobox T val);
> }
> ```
>
> ```d
> module core.boxing.slice;
>
> struct BoxedSlice(T) {
>   T[] slice;
>   alias slice this;
>
>   bool empty() {
>       return slice.length == 0;
>   }
> }
> ```
>
> The reason I am suggesting this is we clearly don't like the solutions
> being proposed for the range functions for slices. So lets change it to
> a problem we do like and can solve.

I see nothing positive about having to wrap arrays. Treating arrays as
ranges is far more user-friendly than that and has worked very well for us
overall. It's just annoying to have to import a module in order to be able
to use the range API with arrays, and auto-decoding was a mistake. Other
than that, it really hasn't been an issue.

In order to get rid of save, the range API has to change enough that the old
basic input ranges don't look like the new forward ranges, so it can't stay
identical to what it is now regardless. Changing the names of the range API
functions takes care of that, _and_ it allows us to put the new range API
functions for dynamic arrays in object.d without breaking a ton of code.

Yes, having to rename the functions is annoying, but it also has the
advantage of making it clear whether code is using the updated range API
with the adjusted semantics or whether the code was written for the old
range API and does not necessarily conform to the new requirements. So,
renaming the range API functions fixes multiple problems.

Making it so that arrays must be wrapped to be ranges does nothing to fix
the need to at least partially change the range API so that the old basic
input ranges don't look like the new forward ranges. So, it doesn't fix the
need to either rename the functions or add an otherwise pointless enum to
the range API just to indicate that it's not the old range API. And if we're
going to rename them, we might as well just rename them all so that the
difference is clear, and it gives us an easy way to fix the import problem
for treating arrays as ranges.

So, I don't see anything here that wrapping arrays improves. It just causes
additional friction, and if you have to import anything to create the
wrapper, we're right back to where we are now with having to import
std.range.primitives, just with a different module.

I'm also highly skeptical that it would work to convince Walter to add an
auto-boxing feature for this. He specifically complained in a recent DLF
meeting about how we can't just use arrays as ranges with no need to do
anything like import a module. He wants to be able to use the range API with
arrays with no friction, and renaming the range API functions gives us a way
to do that, whereas wrapping them creates additional friction.

On top of that, alias this is almost always a mistake with generic code.
Most generic code tests for specific types, not implicit conversions, and
testing for implicit conversions is error-prone, because the implicit
conversion doesn't actually happen just because it's tested for, and even if
it does happen, because the code forced the conversion, it happens inside
the function rather than at the call site, which risks all kinds of issues
(not as many with dynamic arrays as with some other types, but implicit
conversions and template constraints are typically a very bad combination).
So, adding a wrapper type is going to cause all kinds of problems with
template constraints and which overloads get used. The whole situation is
just much simpler if we don't wrap dynamic arrays.

All in all, I do not understand why you think that wrapping arrays improves
anything. From what I can see, it's just purely worse.

Yes, Walter has to be convinced to add first/popFirst/isEmpty to foreach in
addition to front/popFront/empty for the proposed range API to work with
foreach, but given that it explicitly fixes a problem that he was
complaining about, and it's a very simple change, I expect that he'll
approve it. But even if he doesn't, and we're forced to do something like
put an otherwise pointless enum on the new range API to distinguish new
ranges from the old ones, I'd still rather be importing the replacement for
std.range.primitives than have to deal with a wrapper type.

- Jonathan M Davis





More information about the Digitalmars-d mailing list