June 20, 2020
On 6/20/20 3:23 PM, Jon Degenhardt wrote:
> On Saturday, 20 June 2020 at 18:26:43 UTC, Steven Schveighoffer wrote:
>> But the larger point is that true input-only ranges are rare. The only problem is for classes, since you cannot invoke a copy constructor on those.
> 
> Interesting discussion. Could you expand on this comment? Several people have mentioned this.

There are a few "base" ranges, like arrays, and data structure ranges. The one common true "input-only" range is a stream-based range (like File.byLine).

Other than that, wrapping ranges should implement the primitives that their dependencies do.

I'd say arrays are the most common range, and used for almost everything. Even with file streams, most of the time, you don't use the stream as a range, but use algorithms on the buffered data (which is an array).

An input range is generally useful in foreach, and almost nothing else.

> 
> I write my own input ranges somewhat regularly. I've never had the need to make them forward ranges. However, the typical reason for creating a range is because I have application specific data that I want to iterate over (and usually construct) lazily. Input ranges are very convenient way to do this. I do end up making many of them reference ranges though.

Many people do not bother adding ".save" because it's an extra thing, and copying works fine even if you don't declare save.

> 
> So, I'm wondering if its really that input-only ranges are rare, or if it's that the number of algorithms that can be used on input-only ranges is small. Or perhaps I'm not quite grokking the distinction between a "true" input-only range and one that satisfies isInputRange, but none of the other range primitive tests.

An input range cannot be (correctly) iterated more than once, even if you make copies of it.

-Steve
June 20, 2020
On Saturday, 20 June 2020 at 20:09:56 UTC, Steven Schveighoffer wrote:
> On 6/20/20 3:16 PM, Stanislav Blinov wrote:
>> There should be a `move` at that return. There's no NRVO, the compiler makes a copy there.  Which, of course, it wouldn't be able to if R was non-copyable :)
>
> Yeah, it needs to use move. But the usage doesn't change, which is what I thought you were referring to.

The usage does change. Because then (i.e. if input ranges were to be made non-copyable):

auto r = makeInputRange;
auto rem = r.find!pred;

...no longer compiles. And *that* will be all over Phobos, which is my original point about required massive changes. Which, as it turns out, may not be that bad of a thing in principle (though, of course, going over all parts of Phobos that touch ranges to fix them would be a huge endeavor).

>> It *may* become a move under Walter's proposal:
>> but until then?..
>
> You'd have to modify Phobos.

Exactly.

>>> There have been a few libraries released that use non-copyable structs to model things that making copies of will prove problematic. For instance, file handles. And people seem to be able to use these reasonably well.
>> 
>> We're talking ranges here, not file handles :) Most algorithms in Phobos take ranges by value.

> Which means that you have to destroy the original, because it's no longer valid.
>
> Today, you do:
>
> auto otherRange = inputRange.find(something);
>
> use(inputRange); // invalid, but compiler lets you. And it probably messes up otherRange too.

Yeah, that's a good point, thanks.

>>> But the larger point is that true input-only ranges are rare. The only problem is for classes, since you cannot invoke a copy constructor on those.
>>>
>>> It would be a drastic change, I don't see it happening. But the status quo of save() is pretty bad as well.
>> 
>> *Why* is it bad?
>
> Because there's no enforcement. You can copy most forward ranges without using save, and it works EXACTLY the same. In fact, most save implementations are {return this;}. If you ever use a forward range that requires the use of .save to actually copy it, you will have problems.
>
> So it essentially promotes generic code that is incorrect, but works fine for most cases. I'm positive there are still cases like this in Phobos. It's very easy and effortless to make a copy without using .save.
>
> When's the last time you saw:
>
> foreach(x; someLvalueRange.save)
>
> Because if you don't see that, for a forward range, it's an incorrect usage.

In that case, making input ranges non-copyable *may* indeed be a solution.
June 20, 2020
On Saturday, 20 June 2020 at 20:17:29 UTC, Steven Schveighoffer wrote:
> On 6/20/20 3:23 PM, Jon Degenhardt wrote:
>> On Saturday, 20 June 2020 at 18:26:43 UTC, Steven Schveighoffer wrote:
>>> But the larger point is that true input-only ranges are rare. The only problem is for classes, since you cannot invoke a copy constructor on those.
>> 
>> Interesting discussion. Could you expand on this comment? Several people have mentioned this.
>
> There are a few "base" ranges, like arrays, and data structure ranges. The one common true "input-only" range is a stream-based range (like File.byLine).

Thanks for the detailed reply, it's very helpful and I'm finding the discussion useful.

Another potentially useful example is a stream of random numbers, where it may be undesirable to allow the state of the random number stream to be copied.

> An input range is generally useful in foreach, and almost nothing else.

Here I might disagree a bit. Nearly all the algorithms in std.algorithm.iteration (map, filter, fold, etc.) operate on input-only ranges. These are important classes of operations. They are largely alternate forms of foreach, so likely this is more clarification than disagreement.

Related thought: One-time iteration of a stream is common in many of the apps I write. I tend to think of such streams as logically input-only. Whether it's valid/legal to copy the stream at implementation level is a different question. More recently I've finding reasons to enforce at most once iteration, even if the underlying data structures (like an array) don't care.

--Jon


June 20, 2020
On Saturday, 20 June 2020 at 20:13:26 UTC, Walter Bright wrote:
> On 6/14/2020 9:18 AM, Avrina wrote:
>> Size doesn't matter if it doesn't work.
>
> I use it all the time, it works fine.

This one is preventing me from using optlink: https://issues.dlang.org/show_bug.cgi?id=15213

—Bastiaan
June 20, 2020
On Saturday, 20 June 2020 at 18:33:46 UTC, Steven Schveighoffer wrote:
> [snip]
>
> Sorry, this is not the same.
>
> Cursors are simply a way to refer to exactly one element, instead of using a begin and end element like a traditional container range would.
>
> The advantage is that removing the "end" element (that has nothing to do with the range) won't invalidate the cursor.
>
> What I wanted was the functionality of C++ iterators without the danger that comes with actually iterating them.
>
> -Steve

Andrei's proposal was for a function that returns true and fills the referred to variable and returns false otherwise. Cursors are obviously different, but they have a similarity in that a simple cursor, like below, has true/false behavior by storing it in empty.

struct Cursor(T) {
    T* ptr;
    bool empty = false;
}

That aspect of it was what I was thinking of. So for instance, if you have a struct that has a Cursor as a member, then you could do something like below.
bool fetchNext(ref T target) {
    cursor.popFront; //need to define popFront
    target = cursor.ptr;
    return cursor.empty;
}
June 20, 2020
On 6/20/20 3:23 PM, Jon Degenhardt wrote:
> On Saturday, 20 June 2020 at 18:26:43 UTC, Steven Schveighoffer wrote:
>> But the larger point is that true input-only ranges are rare. The only problem is for classes, since you cannot invoke a copy constructor on those.
> 
> Interesting discussion. Could you expand on this comment? Several people have mentioned this.
> 
> I write my own input ranges somewhat regularly. I've never had the need to make them forward ranges. However, the typical reason for creating a range is because I have application specific data that I want to iterate over (and usually construct) lazily. Input ranges are very convenient way to do this. I do end up making many of them reference ranges though.
> 
> So, I'm wondering if its really that input-only ranges are rare, or if it's that the number of algorithms that can be used on input-only ranges is small. Or perhaps I'm not quite grokking the distinction between a "true" input-only range and one that satisfies isInputRange, but none of the other range primitive tests.

Numbers are kinda in your favor. Without getting anywhere near scientific about it:

git grep isInputRange | wc -l
     601
git grep isForwardRange | wc -l
     369
git grep isBidirectionalRange | wc -l
     167
git grep isRandomAccessRange | wc -l
     285
June 20, 2020
On 6/20/20 12:41 PM, Stanislav Blinov wrote:
> Being able to call front() several times does not necessitate the *range* being buffering.

If one calls front() twice without advancing the range, where does the range return the value from?

Input ranges must buffer one element at least (input iterators in STL, too). This has been a source of annoyance in implementing them.
June 20, 2020
On 6/20/20 1:02 PM, Avrina wrote:
> Most of the algorithms in Phobos don't call front() multiple times and they make a copy.

Interesting. They should be easy to fix then. Got a few examples?
June 20, 2020
On 6/20/20 2:26 PM, Steven Schveighoffer wrote:
> It would be a drastic change, I don't see it happening.

I totally see it happening if we go with versioning.

D has this good module system, so it should work real nice. To the extent it doesn't, it would point to opportunities for improvement.

The stalemate we're in has taken long enough.
June 20, 2020
On 6/20/20 2:34 PM, Johannes Loher wrote:
> On Saturday, 20 June 2020 at 13:07:41 UTC, Paul Backus wrote:
>> On Saturday, 20 June 2020 at 12:42:01 UTC, Stanislav Blinov wrote:
>>> On Saturday, 20 June 2020 at 10:43:41 UTC, Paul Backus wrote:
>>>
>>>> Also, switch from `void popFront()` to `typeof(this) rest`, so that we can have `const` and `immutable` ranges.
>>>
>>> *Switch* is probably too restrictive. For a given range a `popFront` may be more efficient than a `range = range.rest`. Making `rest` a valid range primitive though, and using where appropriate - that'd be awesome.
>>
>> For non-forward ranges, there's no promise that the original range (or any copies of it) will remain valid after calling `rest`, so you can always implement `rest` like this:
>>
>>     auto rest() {
>>         this.popFront;
>>         return this;
>>     }
> 
> Can’t make it const then... also some ranges probably cannot bee const by design, e.g. byLine etc.

That function would be a fallback. Ranges that are const would implement it as a primitive not expressible with popFront.

Consider:

immutable List(T) {
    private T payload;
    private List next;
    public List tail() { return next; }
    ...
}

You can't implement popFront for this structure.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18