November 02, 2021
On Wed, Nov 03, 2021 at 12:18:59AM +0000, Paul Backus via Digitalmars-d wrote:
> On Wednesday, 3 November 2021 at 00:01:33 UTC, H. S. Teoh wrote:
> > The problem with manually-added tags of this sort is that people forget to do it, and that leads to trouble.  Preferably, it should be something already implicit in the range type itself, that does not require additional effort to tag.
> > 
> > I'm kinda toying with the idea of struct == forward range, class == input range: the difference is inherent in the type itself and requires no further effort beyond the decision to use a by-value type vs. a by-reference type, which coincides with the decision to make something an input range or a forward range.
> 
> Having input ranges implement `next` and forward ranges implement `head` and `tail` would also make them easy to distinguish.

That would work too, but makes the input range API no longer a subset of the forward range API.  This would lead to code duplication in algorithms that only require an input range but could work equally well with a forward range.


T

-- 
Once bitten, twice cry...
November 03, 2021
On Wednesday, 3 November 2021 at 00:24:11 UTC, H. S. Teoh wrote:
> On Wed, Nov 03, 2021 at 12:18:59AM +0000, Paul Backus via Digitalmars-d wrote:
>>
>> Having input ranges implement `next` and forward ranges implement `head` and `tail` would also make them easy to distinguish.
>
> That would work too, but makes the input range API no longer a subset of the forward range API.  This would lead to code duplication in algorithms that only require an input range but could work equally well with a forward range.

Not necessarily. It's possible to implement `next` as a UFCS function for mutable forward ranges using the `head`/`tail` API:

auto next(R)(ref R r)
    if (isForwardRangeV2!R && isMutable!R)
{
    alias E = ElementType!R;
    if (r.empty)
        return none!E();
    else
    {
        auto result = some(r.head);
        r = r.tail;
        return result;
    }
}
November 02, 2021

On 11/2/21 8:11 AM, Dukc wrote:

>
  • We also add some other function, or perhaps a flag to aforementioned one, that can convert any v1 input ranges to v2 input range. valueRange as default must not accept non-forward ranges, because then it cannot guarantee that the result will be a value range.

How does phobos v2 view a non-class non-forward v1 range? This is the fundamental problem needing solving, because if you make copyability the defining trait, current v1 input-only ranges (e.g. File.byLine) are going to be miscategorized.

-Steve

November 03, 2021
On Tuesday, 2 November 2021 at 20:17:06 UTC, Andrei Alexandrescu wrote:
> On 2021-11-02 15:32, Adam D Ruppe wrote:
>> On Tuesday, 2 November 2021 at 18:09:55 UTC, H. S. Teoh wrote:
>>> Why is this necessary?  I thought we're getting rid of std.range.interfaces.
>> 
>> It is actually really, really, useful. If phobos didn't offer it, someone would reinvent it anyway.
>> 
>> (In fact, there's a lot of cases where using them is more efficient than generating more and more code...)
>
> Yah, polymorphism has its place. The only problem is passing around reference ranges. They should have a thin struct wrapper that carries the proper copy semantics.

So, if forward range interface (from std.range.interfaces) is to be kept in phobos, it should provide a .save method, that can be used instead of copy constructor.

Then, it is possible to have only one wrapper struct for transforming it into value type (i.e. behave same as struct forward range), that would use .save when wrapper's copy constructor is invoked. It would allow to use this wrapper as part of method parameter type, in order to enforce people using it, and not randomly forgetting to wrap it.
November 03, 2021

On Wednesday, 3 November 2021 at 02:34:00 UTC, Steven Schveighoffer wrote:

>

On 11/2/21 8:11 AM, Dukc wrote:

>
  • We also add some other function, or perhaps a flag to aforementioned one, that can convert any v1 input ranges to v2 input range. valueRange as default must not accept non-forward ranges, because then it cannot guarantee that the result will be a value range.

How does phobos v2 view a non-class non-forward v1 range? This is the fundamental problem needing solving, because if you make copyability the defining trait, current v1 input-only ranges (e.g. File.byLine) are going to be miscategorized.

-Steve

They are v2 input ranges. v2 input ranges are not required to be value ranges, but should be if they can (meaning, if their v1 equivalent would be a forward range).

The reason I said that valueRange by default must not return a reference range is because it'd conflict with the function name.

November 03, 2021

On Wednesday, 3 November 2021 at 00:18:59 UTC, Paul Backus wrote:

>

Having input ranges implement next and forward ranges implement head and tail would also make them easy to distinguish.

There's an easier solution: require all v2 ranges to have the inputRangeTag. For forward ranges it must be set to false, but if the tag does not exist at all then it isn't a v2 range.

If we go for this, I'd rename the tag to isReferenceRange though.

It's going to require more manual usage of the valueRange wrapper though, as the v1 ranges around aren't going to be v2 ranges as often.

What do you say?

November 03, 2021

On Wednesday, 3 November 2021 at 11:54:24 UTC, Dukc wrote:

>

There's an easier solution: require all v2 ranges to have the inputRangeTag. For forward ranges it must be set to false, but if the tag does not exist at all then it isn't a v2 range.

If we go for this, I'd rename the tag to isReferenceRange though.

It's going to require more manual usage of the valueRange wrapper though, as the v1 ranges around aren't going to be v2 ranges as often.

What do you say?

Sure, that would work. But that still leaves the issue of const ranges, which is the other thing head and tail are meant to address.

I think if we are going to make incompatible changes to the range API, we might as well do a proper redesign that fixes all of the known issues at once. And until we do that (maybe in v3?), it is probably better to hold off on incompatible changes.

November 03, 2021
On 2021-11-02 19:07, Paul Backus wrote:
> On Tuesday, 2 November 2021 at 21:58:20 UTC, Andrei Alexandrescu wrote:
>> On 2021-11-02 17:44, Dukc wrote:
>>>
>>>> What's a value range?
>>>
>>> Opposite of a reference range - copying implies `save()`.
>>
>> Yah, one simple improvement we could make is to assume all forward ranges copy their iteration state when copying the range. Then input ranges do NOT do that, i.e. all copies of an input range refer to the same stream and iterate it together (advancing one advances all).
>>
>> The differentiation can be made with a nested enum tag:
>>
>> struct MyInputRange {
>>     enum inputRangeTag = true;
>>     ...
>> }
>>
>> Client code can inspect R.inputRangeTag to figure whether the range is input (if present) or forward (if missing).
> 
> Not sure this is the best idea--it means new-style algorithms will silently treat old-style input ranges as though they were forward ranges, which could lead to incorrect behavior at runtime. If we are going to make incompatible changes to the range API, we should do it in such a way that version mismatches are caught at compile time.

Good point. Maybe have all ranges define that enum with values true and false respectively?
November 03, 2021

On Wednesday, 3 November 2021 at 15:03:53 UTC, Andrei Alexandrescu wrote:

>

[snip]

Good point. Maybe have all ranges define that enum with values true and false respectively?

Maybe I just haven't been following this discussion closely enough, but I'm a little confused by this. We have __traits(hasMember, T, inputRangeTag) after all. There should be default behavior when that tag isn't there and more sophisticated behavior when it is (if it is needed).

November 03, 2021
On 2021-11-02 20:18, Paul Backus wrote:
> On Wednesday, 3 November 2021 at 00:01:33 UTC, H. S. Teoh wrote:
>> The problem with manually-added tags of this sort is that people forget to do it, and that leads to trouble.  Preferably, it should be something already implicit in the range type itself, that does not require additional effort to tag.
>>
>> I'm kinda toying with the idea of struct == forward range, class == input range: the difference is inherent in the type itself and requires no further effort beyond the decision to use a by-value type vs. a by-reference type, which coincides with the decision to make something an input range or a forward range.
> 
> Having input ranges implement `next` and forward ranges implement `head` and `tail` would also make them easy to distinguish.

What would be the signature of next?

Would forward ranges also implement next? If not, that would mean algorithms for input ranges won't work for forward ranges.