Jump to page: 1 25  
Page
Thread overview
Forward ranges in Phobos v2
Nov 01, 2021
Dukc
Nov 01, 2021
H. S. Teoh
Nov 01, 2021
Dukc
Nov 01, 2021
Alexandru Ermicioi
Nov 01, 2021
H. S. Teoh
Nov 02, 2021
Alexandru Ermicioi
Nov 02, 2021
Paul Backus
Nov 02, 2021
Dukc
Nov 02, 2021
H. S. Teoh
Nov 02, 2021
Adam D Ruppe
Nov 02, 2021
H. S. Teoh
Nov 02, 2021
Adam D Ruppe
Nov 02, 2021
H. S. Teoh
Nov 03, 2021
Alexandru Ermicioi
Nov 02, 2021
Dukc
Nov 02, 2021
Paul Backus
Nov 03, 2021
H. S. Teoh
Nov 03, 2021
Paul Backus
Nov 03, 2021
H. S. Teoh
Nov 03, 2021
Paul Backus
Nov 03, 2021
Paul Backus
Nov 04, 2021
Paul Backus
Nov 04, 2021
Paul Backus
Nov 04, 2021
Paul Backus
Nov 04, 2021
H. S. Teoh
Nov 05, 2021
Atila Neves
Nov 09, 2021
H. S. Teoh
Nov 05, 2021
Atila Neves
Nov 04, 2021
Dukc
Nov 04, 2021
Paul Backus
Nov 04, 2021
Dukc
Nov 03, 2021
Dukc
Nov 03, 2021
Paul Backus
Nov 03, 2021
jmh530
Nov 03, 2021
Adam D Ruppe
Nov 03, 2021
Dukc
November 01, 2021

It seems we're going to do Phobos v2. I don't know whether it will be the idea Andrei just published or something else, but anyway.

So I think it's the time to discuss, do we want to change the definition of forward ranges? It seems to be the consensus that it's current API is error-prone to use correctly.

For example, we could define that a range that does not offer save() is still a forward range if it can be copy constructed, and that copy constructors for reference ranges would be forbidden. But one big problem with that: classes can not be ranges then.

At the very least, I think we must take the stance that all Phobos v2 ranges must be save-on-copy where the documentation does not explicitly declare them reference ranges.

Ideas?

November 01, 2021
On Mon, Nov 01, 2021 at 01:51:32PM +0000, Dukc via Digitalmars-d wrote:
> It seems we're going to do Phobos v2. I don't know whether it will be the idea Andrei just published or something else, but anyway.
> 
> So I think it's the time to discuss, do we want to change the
> definition of forward ranges? It seems to be the
> [consensus](https://forum.dlang.org/thread/ztgtmumenampiobbuiwd@forum.dlang.org?page=1)
> that it's current API is error-prone to use correctly.
> 
> For example, we could define that a range that does not offer `save()` is still a forward range if it can be copy constructed, and that copy constructors for reference ranges would be forbidden. But one big problem with that: classes can not be ranges then.
> 
> At the very least, I think we must take the stance that all Phobos v2 ranges must be save-on-copy where the documentation does not explicitly declare them reference ranges.
[...]

Based on what Andrei said in the past, there will no longer be .save (which has always been a point of confusion in the API and interacts poorly with the type system), but forward ranges will be based on by-value semantics, and input ranges on by-reference semantics. So if it's a by-value type, it's a forward range; if it's a by-reference type, it's an input range.

If you have a struct but it only supports input range semantics, then you could pass it by ref or pass a pointer to it.

If you have a class but it supports forward range semantics, then wrap it in a struct with a copy ctor that saves state in the copy ctor.


T

-- 
Questions are the beginning of intelligence, but the fear of God is the beginning of wisdom.
November 01, 2021

On Monday, 1 November 2021 at 14:13:00 UTC, H. S. Teoh wrote:

>

Based on what Andrei said in the past, there will no longer be .save (which has always been a point of confusion in the API and interacts poorly with the type system), but forward ranges will be based on by-value semantics, and input ranges on by-reference semantics. So if it's a by-value type, it's a forward range; if it's a by-reference type, it's an input range.

If you have a struct but it only supports input range semantics, then you could pass it by ref or pass a pointer to it.

If you have a class but it supports forward range semantics, then wrap it in a struct with a copy ctor that saves state in the copy ctor.

T

Simple and effective! I think that's what I'm voting for.

November 01, 2021
On Monday, 1 November 2021 at 14:13:00 UTC, H. S. Teoh wrote:
> If you have a struct but it only supports input range semantics, then you could pass it by ref or pass a pointer to it.
>
> If you have a class but it supports forward range semantics, then wrap it in a struct with a copy ctor that saves state in the copy ctor.
>
>
> T

What about random access range?

If you don't care for the implementation of the range but only being a forward range, how would you express it as a parameter for a method?

Best regards,
Alexandru.
November 01, 2021
On Mon, Nov 01, 2021 at 11:34:20PM +0000, Alexandru Ermicioi via Digitalmars-d wrote:
> On Monday, 1 November 2021 at 14:13:00 UTC, H. S. Teoh wrote:
> > If you have a struct but it only supports input range semantics, then you could pass it by ref or pass a pointer to it.
> > 
> > If you have a class but it supports forward range semantics, then wrap it in a struct with a copy ctor that saves state in the copy ctor.
[...]
> What about random access range?

Presumably, it will be a struct with additional methods needed for
random access (opIndex, et al).


> If you don't care for the implementation of the range but only being a forward range, how would you express it as a parameter for a method?
[...]

Good question, ask Andrei. ;-)

Presumably, if we standardize on structs/classes, it could be as simple as:

	auto myFunc(R)(R range) if (is(R == struct)) {
		... // forward range
	}

	auto myFunc(R)(R range) if (is(R == class)) {
		... // input range
	}

But given that Andrei thinks it's a mistake for ranges to be implemented as classes, I've no idea.


T

-- 
Don't modify spaghetti code unless you can eat the consequences.
November 02, 2021
On Monday, 1 November 2021 at 23:46:07 UTC, H. S. Teoh wrote:
> Good question, ask Andrei. ;-)

Well, I hope he will check this thread and comment on it.

> Presumably, if we standardize on structs/classes, it could be as simple as:
>
> 	auto myFunc(R)(R range) if (is(R == struct)) {
> 		... // forward range
> 	}
>
> 	auto myFunc(R)(R range) if (is(R == class)) {
> 		... // input range
> 	}
>
> But given that Andrei thinks it's a mistake for ranges to be implemented as classes, I've no idea.

That would work, if you have templated funcs, but what if you need it in an interface?

If class based ranges are to be in D language, I doubt it will be possible to avoid .save function completely. At least for range interfaces, the save of forward range will have to be expressed through a method, such as .save.


November 02, 2021
On Tuesday, 2 November 2021 at 00:05:48 UTC, Alexandru Ermicioi wrote:
> On Monday, 1 November 2021 at 23:46:07 UTC, H. S. Teoh wrote:
>> Good question, ask Andrei. ;-)
>
> Well, I hope he will check this thread and comment on it.
>
>> Presumably, if we standardize on structs/classes, it could be as simple as:
>>
>> 	auto myFunc(R)(R range) if (is(R == struct)) {
>> 		... // forward range
>> 	}
>>
>> 	auto myFunc(R)(R range) if (is(R == class)) {
>> 		... // input range
>> 	}
>>
>> But given that Andrei thinks it's a mistake for ranges to be implemented as classes, I've no idea.
>
> That would work, if you have templated funcs, but what if you need it in an interface?
>
> If class based ranges are to be in D language, I doubt it will be possible to avoid .save function completely. At least for range interfaces, the save of forward range will have to be expressed through a method, such as .save.

You can always wrap a class/interface method in a struct that calls .save on copy:


struct ClassRangeWrapper(T)
    if (is(T == class) || is(T == interface))
{
    T payload;
    alias payload this;

    this(ref inout typeof(this) other) inout
    {
        this.payload = other.payload.save;
    }
}

This way, range algorithms don't need to know about .save, so it can be removed from the official forward range requirements even though it still exists as an implementation detail.
November 01, 2021
On 11/1/21 8:13 PM, Paul Backus wrote:
> On Tuesday, 2 November 2021 at 00:05:48 UTC, Alexandru Ermicioi wrote:
>> On Monday, 1 November 2021 at 23:46:07 UTC, H. S. Teoh wrote:
>>> Good question, ask Andrei. ;-)
>>
>> Well, I hope he will check this thread and comment on it.
>>
>>> Presumably, if we standardize on structs/classes, it could be as simple as:
>>>
>>>     auto myFunc(R)(R range) if (is(R == struct)) {
>>>         ... // forward range
>>>     }
>>>
>>>     auto myFunc(R)(R range) if (is(R == class)) {
>>>         ... // input range
>>>     }
>>>
>>> But given that Andrei thinks it's a mistake for ranges to be implemented as classes, I've no idea.
>>
>> That would work, if you have templated funcs, but what if you need it in an interface?
>>
>> If class based ranges are to be in D language, I doubt it will be possible to avoid .save function completely. At least for range interfaces, the save of forward range will have to be expressed through a method, such as .save.
> 
> You can always wrap a class/interface method in a struct that calls .save on copy:

Exactly. No need to support class ranges - simple wrappers can do everything class-like indirection does. Thanks.

November 02, 2021

On Tuesday, 2 November 2021 at 02:45:11 UTC, Andrei Alexandrescu wrote:

>

Exactly. No need to support class ranges - simple wrappers can do everything class-like indirection does. Thanks.

Trying to write up a plan based on that one, so you can correct and/or spot weaknesses

  • stuff in std.v2.range.interfaces and std.v2.concurrency.Generator will continue to be ranges from Phobos v1 viewpoint but not from Phobos v2 viewpoint.

  • We add a function, let's say std.range.valueRange, in both versions, that will convert any v1 forward range to a value range that works in both versions.

  • 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.

  • We need some way to prevent Phobos v2 using v1 reference forward ranges accidently. Making v2 isInputRange to be an automatic negative for classes can suffice for now.

  • Phobos v2 ranges should still continue to provide the save method so they can be passed to v1 ranges. We also might provide an assumeValueRange function that will add the save method on top of any existing input range, assuming value semantics and making it a forward range from v1 perspective.

November 02, 2021
On Tue, Nov 02, 2021 at 12:11:24PM +0000, Dukc via Digitalmars-d wrote:
> On Tuesday, 2 November 2021 at 02:45:11 UTC, Andrei Alexandrescu wrote:
> > 
> > Exactly. No need to support class ranges - simple wrappers can do everything class-like indirection does. Thanks.
> 
> Trying to write up a plan based on that one, so you can correct and/or spot weaknesses
> 
> - stuff in `std.v2.range.interfaces` and
>   `std.v2.concurrency.Generator` will continue to be ranges from
>   Phobos v1 viewpoint but not from Phobos v2 viewpoint.

Why is this necessary?  I thought we're getting rid of std.range.interfaces.


> - We add a function, let's say `std.range.valueRange`, in both
>   versions, that will convert any v1 forward range to a value range
>   that works in both versions.

What's a value range?


> - 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.
[...]

Interesting idea. So basically a shim for easy translation of v1-based code to v2-based code?  That would be nice for gradual migration.  It would have to exclude certain incompatible things like autodecoded strings, though. Otherwise it will result in a mess.


> - Phobos v2 ranges should still continue to provide the `save` method
>   so they can be passed to v1 ranges.
[...]

I'm not sure this is such a good idea, because v2 ranges may have fundamental incompatibilities with v1 algorithms, e.g., a v2 string range (non-autodecoded) being passed to a v1 algorithm (autodecoded) will probably produce the wrong results, likely silently, which is bad. Now imagine mixing v1 algorithms and v2 algorithms in the same UFCS chain (via shims) over a string, and you're in for a heck of time trying to debug the resulting mess.

IMO it's better to just keep v1 code distinct from v2 code, and migrate v1-based code to v2-based code on a case-by-case basis.  In most cases, you could probably just change `import std` to `import stdv2` and it should work.  In cases involving e.g. autodecoding you'd add an adapter or two in your UFCS code, then change to `import stdv2` and that should fix it.  For the rest of the cases, just leave `import std` as-is, and existing code should still function as before, with existing semantics, without any surprise breakages.


T

-- 
That's not a bug; that's a feature!
« First   ‹ Prev
1 2 3 4 5