Must ranges support `r1 = r2;`?
ag0aep6g
anonymous at example.com
Sat Mar 24 23:15:51 UTC 2018
On 03/24/2018 11:36 PM, Simen Kjærås wrote:
> void main()
> {
> import std.range;
> import std.stdio;
>
> string s = "foo";
> auto r = refRange(&s);
>
> auto r2 = r;
> r2 = r2.save;
> /* Surprising: Effectively just does `s = s;` (i.e., nothing). */
>
> r2.popFront();
> writeln(r); /* Surprising: Prints "oo". */
> }
>
> None of this is surprising. When you call r2.popFront(), it's being
> forwarded to s.popFront. That's what refRange does, it's what it's
> supposed to do, and it's the only thing it could sensibly do.
I think it's surprising. It works as intended and as documented, but
it's surprising. Note that the behavior is different when you change
auto r2 = r;
r2 = r2.save;
to
auto r2 = r.save;
`.save` actually makes a new slice that can be popped independently from
`s`. But the assignment throws that independent slice away.
> This is conceptually what refRange does:
>
> struct RefRange(R) {
> R* innerRange;
> this(R* innerRange) {
> this.innerRange = innerRange;
> }
> void popFront() {
> innerRange.popFront();
> }
> @property auto front() {
> return innerRange.front;
> }
> @property bool empty() {
> return innerRange.empty;
> }
> }
That's all fine. You're missing the interesting bit: opAssign. `save`
might also be interesting, but the actual `save` is perfectly fine.
[...]
> So when you do this:
>
> string s = "foo";
> auto r = refRange(&s).group;
> writeln(r.save);
>
> r.save is going to create a new Group range, which contains a RefRange,
> which ultimately points to s. writeln() then repeatedly calls
> popFront(). This mutates s, as specified above.
That's not how it should be. A saved range should be iterable
independently from the original. That's the point of `.save`. If that's
not possible, `.save` should not be there.
You're implying that a saved RefRange points to the same range as the
original. That's not true. RefRange.save saves the underlying range and
lets the new RefRange point to that. This is correct. The two ranges can
be iterated independently.
It's inside `Group` that things get messed up. There you have this
innocent looking implementation of `save` [1]:
@property typeof(this) save() {
typeof(this) ret = this;
ret._input = this._input.save;
ret._current = this._current;
return ret;
}
The problem is in the line `ret._input = this._input.save;`. That calls
RefRange.opAssign. If you refactor the code to avoid opAssign, saving
works properly. For example:
private this(typeof(_input) inp, typeof(_current) cur)
{
this._input = inp; /* Initialization, not assigment. */
this._current = cur; /* Ditto, but doesn't matter. */
}
@property typeof(this) save() {
return typeof(this)(this._input.save, this._current);
}
Note that it calls `_input.save` just the same as before. The code only
avoids RefRange.opAssign.
[...]
> The 'chain', 'choose', and 'cycle' examples are an effect of strings not
> being random access ranges. I'm uncertain if we should call this a bug,
> but I agree the behavior is unexpected, so we probably should.
I don't think random access has anything to do with this.
> The splitter example is of course a bug. The crash, that is. The
> expected behavior is that the first writeln prints [foo, ar], which it
> does, and that the second print [].
Again, I don't agree. You're effectively saying that saving a RefRange
isn't possible. If that were so, it simply shouldn't have a `save`
method. But the `save` method works perfectly fine. It's Phobos
(accidentally) calling RefRange.opAssign that breaks things.
[1]
https://github.com/dlang/phobos/blob/3225b19c9bae4b7e8d7a93d2d8c22c94b2a1a6c5/std/algorithm/iteration.d#L1505-L1510
More information about the Digitalmars-d
mailing list