Range Redesign: Copy Semantics

Sebastiaan Koppe mail at skoppe.eu
Mon Jan 22 20:22:07 UTC 2024


On Monday, 22 January 2024 at 04:38:13 UTC, Jonathan M Davis 
wrote:
> On Sunday, January 21, 2024 11:26:37 AM MST Sebastiaan Koppe
>> I have been thinking about having an explicit build and 
>> iteration phase, where priming happens when you switch from 
>> build to iteration.
>>
>> The benefit is that implementers have a clear place where to 
>> prime the range.
>
> Well, arguably, that's really an implementation detail of the 
> range, and it would be pretty annoying if range-based code had 
> to worry about explicitly priming a range (you're basically 
> getting into two phase construction at that point, which tends 
> to be problematic). Ideally, once you have a range, it's ready 
> to use. So, while I could see saying what best practice was on 
> that, I'm not sure that I'd want to require that it be done a 
> specific way.
>
> Part of the problem is that in my experience, it sometimes 
> makes sense to do it one place, whereas with other ranges, it 
> makes more sense to do it somewhere else.

Yes exactly. My point is that if there is an explicit place to do 
priming, that uncertainty would go away.

> That being said, I'm not sure that I've ever written a range 
> that primes anything in empty. Usually, the question is between 
> what goes in front and and what goes in popFront and how much 
> has to go in the constructor for that to work cleanly. And the 
> answer is not always the same.

With 2-phase ranges it would always be the same. Anyway, here are 
some Phobos examples:

- `filter` does its priming in empty, 
https://github.com/dlang/phobos/blob/bf35228426529ab19e5d17a3286d187214bf024a/std/algorithm/iteration.d#L1381

- `filterByDirectional` does a while loop in its constructor, 
https://github.com/dlang/phobos/blob/bf35228426529ab19e5d17a3286d187214bf024a/std/algorithm/iteration.d#L1581

- `chunkBy` calls empty in its constructor, 
https://github.com/dlang/phobos/blob/bf35228426529ab19e5d17a3286d187214bf024a/std/algorithm/iteration.d#L1931

- `substitute` calls empty and popFront in its constructor, 
https://github.com/dlang/phobos/blob/bf35228426529ab19e5d17a3286d187214bf024a/std/algorithm/iteration.d#L6909

Taken together, pretty much anything can happen just by 
constructing a range. You don't even need to iterate it!

> Regardless, the big issues that were the point of this thread 
> were the copy and assignment semantics for ranges and what we 
> would need to do fix those. There are definitely other issues 
> that we're going to have to sort out (e.g. the tail-const 
> issue).

Ultimately it all comes together or it doesn't.

>> What about:
>>
>> ```
>>      bool next(Callable)(Callable c) {
>>          if (empty)
>>              return false;
>>          c(front());
>>          popFront();
>>          return true;
>>      }
>> ```
>>
>> It has the benefit of not needing to unbox a Nullable/Pointer.
>
> Well, that would basically be another sort of opApply, which is 
> an idiom that tends to be pretty hard to understand in 
> comparison to the alternatives.

Actual usual would just use `foreach` of course. But in cases 
where you want to iterate manually you have to deal with the 
added ugliness/complexity, fair.

I do think its easier to access the item by `ref` this way, 
instead of having to do a pointer in some nullable wrapper.

> Getting a nullable type of some kind back is far easier to read 
> and understand, and the only real downside I see to it is that 
> you get bugs if someone tries to access the value when there 
> isn't one. But ranges already have that in general with front 
> and empty, and I don't think that it's been much of an issue in 
> practice.

I don't particularly like APIs that have a requirement to call 
methods in a particular order, I much rather have APIs that can't 
be used wrong. Pit of success and all that.

In practice though, I messed up only a few times, and then added 
a condition on `empty` and continued on.

> We could also go with next and hasNext like Alexandru 
> suggested, in which case, we wouldn't be returning a pointer or 
> nullable type if that's the concern. I'm not sure if that's 
> ultimately better or worse though. In some respects, it would 
> be better to be able to put all of the range logic in a single 
> function, but it would also be nice in some situations to be 
> able to ask whether a basic input range has any elements 
> without having to grab the first one if it's there.

With some sources, determining whether there is a next item 
actually involves *getting* the next item. Instead, like `empty`, 
I think `hasNext` ought to be `const`.


More information about the Digitalmars-d mailing list