February 16, 2020
On Sunday, 16 February 2020 at 12:38:51 UTC, Jonathan M Davis wrote:
> On Sunday, February 16, 2020 3:41:31 AM MST uranuz via Digitalmars-d-learn wrote:
>> I have reread presentation:
>> http://dconf.org/2015/talks/davis.pdf
>> We declare that `pure` input range cannot be `unpoped` and we
>> can't return to the previous position of it later at the time. So
>> logically there is no sence of copying input range at all. So
>> every Phobos algorithm that declares that it's is working with
>> InputRange must be tested for working with range with disabled
> A range that can't be copied is basically useless. Not only do almost all range-based algorithms take their argumenst by value (and thus copy them), but foreach copies any range that it's given, meaning that if a range isn't copyable, you can't even use it with foreach. And since many range-based algorithms function by wrapping one range with another, the ability to copy ranges is fundamental to most range-based code.
This is working fine with disabled postblit...
import std;

struct SS
{
    @disable this(this); // Disabled copy

    bool _empty = false;

    bool empty() @property {
        return _empty;
    }

    void popFront() {
       _empty = true;
    }

    int front() @property { return 10; }
}


void main()
{
    foreach( it; SS() ) { writeln(it); }
}

Am I missing something?
February 16, 2020
On Sunday, February 16, 2020 7:29:11 AM MST uranuz via Digitalmars-d-learn wrote:
> On Sunday, 16 February 2020 at 12:38:51 UTC, Jonathan M Davis
>
> wrote:
> > On Sunday, February 16, 2020 3:41:31 AM MST uranuz via
> >
> > Digitalmars-d-learn wrote:
> >> I have reread presentation:
> >> http://dconf.org/2015/talks/davis.pdf
> >> We declare that `pure` input range cannot be `unpoped` and we
> >> can't return to the previous position of it later at the time.
> >> So
> >> logically there is no sence of copying input range at all. So
> >> every Phobos algorithm that declares that it's is working with
> >> InputRange must be tested for working with range with disabled
> >
> > A range that can't be copied is basically useless. Not only do almost all range-based algorithms take their argumenst by value (and thus copy them), but foreach copies any range that it's given, meaning that if a range isn't copyable, you can't even use it with foreach. And since many range-based algorithms function by wrapping one range with another, the ability to copy ranges is fundamental to most range-based code.
>
> This is working fine with disabled postblit...
> import std;
>
> struct SS
> {
>      @disable this(this); // Disabled copy
>
>      bool _empty = false;
>
>      bool empty() @property {
>          return _empty;
>      }
>
>      void popFront() {
>         _empty = true;
>      }
>
>      int front() @property { return 10; }
> }
>
>
> void main()
> {
>      foreach( it; SS() ) { writeln(it); }
> }
>
> Am I missing something?

That code compiles, because you're passing a temporary to foreach. So, the compiler does a move instead of a copy. It's the difference between

auto ss = SS();

and

SS ss;
auto ss2 = ss;

If your main were

    void main()
    {
        SS ss;
        foreach( it; ss ) { writeln(it); }
    }

then it would not compile.

    foreach(e; range) {...}

basically gets lowered to

    for(auto __r = range; !__r.empty; __r.popFront())
    {
        auto e = __r.front;
        ...
    }

So,

    foreach( it; SS() ) { writeln(it); }

would become

    for(auto __r = SS(); !__r.empty; __r.popFront())
    {
        auto it = __r.front;
        writeln(it);
    }

whereas

    SS ss;
    foreach( it; ss ) { writeln(it); }

would become

    SS ss;
    for(auto __r = ss; !__r.empty; __r.popFront())
    {
        auto it = __r.front;
        writeln(it);
    }

- Jonathan M Davis



February 16, 2020
On Sunday, February 16, 2020 6:52:17 AM MST uranuz via Digitalmars-d-learn wrote:
> It's very bad. Because there seem that when I use range based
> algorithm I need to take two things into account. The first is
> how algrorithm is implemented. If it creates copies of range
> inside or pass it by reference. And the second is how the range
> is implemented if it has value or reference semantics. So every
> time I need to look into implementation and I can't rely on API
> description in most of the cases. In a lot of cases Phobos uses
> value semantics. But there are cases where I want the range
> actually be consumed, but it's not. And the other problemme is
> when algorithm expects range to have value semantics, but it's
> not. So it's a buggy mess that it's hard to think about. In
> trivial cases this is working although. But in more complex cases
> it's simplier to implement some algorithms by own hands so that
> it would work as I expect it myself rather that thinking about
> all these value-ref-range mess. But still can't say that I
> implement it correctly, because range specification actually
> sucks as yo say.
> It's just horrible

The current situation is definitely not ideal, but it can be used quite consistently. Just follow the rule that once you copy a range, you do not use that range ever again unless you assign it a new value. So, if you do something like

auto result = r.find(e);

then you should not be using r again unless you assign it a new value. e.g.

r = r.find(e);
r.popFront();

auto result = r.find(e);
r = otherRange;
r.popFront();

would be fine, because r was assigned a new value, whereas

auto result = r.find(e);
r.popFront();

should never happen in your code unless you know that typeof(r) is a type where copying it is equivalent to calling save on it. In generic code, that means that you should never use a range again after passing it to a function unless you assign it a new value, or that function accepted the argument by ref (which almost no range-based functions do).

If you want to be able to use the range again after passing it to a function without assigning it a new value, then you need to call save so that you pass an independent copy. e.g.

auto result = r.find.save(e);
r.popFront();

The only copying going on here is with save, so there's no problem, whereas if r were passed to find directly, the behavior is implementation-dependent - hence why you should not be using a range after it's been copied (which includes passing it to a function).

The only time that generic code can reuse a range after passing it to a function is if that function accepts its argument by ref, which almost no range-based code does. Far more frequently, it returns the result as a wrapper range, in which case, you can't use the original range any longer unless you used save when calling the function or if the code is not generic and you're coding based on the specific behavior of that particular range type - which usually isn't something that code should be doing.

By no means do I claim that the status quo here is desirable, but if you just follow the simple rule that you don't ever use a range once it's been copied (unless that copy came from save), then you shouldn't run into problems related to the fact that different ranges have different copying semantics unless the function that you're calling is buggy.

If you're going to run into a bug along those lines though, it's likely going to be because a function didn't call save when it was supposed to, and it was only tested with range types where copying them is equivalent to save. That's why it's important to test range-based code with both range types where copying them is equivalent to save and range types which are full-on reference types (and thus copying just results in another reference). In general though, any range that is a forward range should have copying it be equivalent to save, and using reference types for forward ranges tends to be inefficient and error-prone even if range-based functions (especially those in Phobos) should be able to handle them correctly.

- Jonathan M Davis



February 16, 2020
On Sunday, 16 February 2020 at 17:10:24 UTC, Jonathan M Davis wrote:
> On Sunday, February 16, 2020 7:29:11 AM MST uranuz via
>> This is working fine with disabled postblit...
>> import std;
>>
>> struct SS
>> {
>>      @disable this(this); // Disabled copy
>>
>>      bool _empty = false;
>>
>>      bool empty() @property {
>>          return _empty;
>>      }
>>
>>      void popFront() {
>>         _empty = true;
>>      }
>>
>>      int front() @property { return 10; }
>> }
>>
>>
>> void main()
>> {
>>      foreach( it; SS() ) { writeln(it); }
>> }
>>
>> Am I missing something?
>
> That code compiles, because you're passing a temporary to foreach. So, the compiler does a move instead of a copy. It's the difference between
>
> auto ss = SS();
>
> and
>
> SS ss;
> auto ss2 = ss;
>
> If your main were
>
>     void main()
>     {
>         SS ss;
>         foreach( it; ss ) { writeln(it); }
>     }
>
> then it would not compile.

On the other hand, this does work:

    void main()
    {
        SS ss;
        foreach( it; move(ss) ) { writeln(it); }
    }

So perhaps the correct approach is to use `move` when copying input ranges.
February 16, 2020
On Sunday, February 16, 2020 10:53:36 AM MST Paul Backus via Digitalmars-d- learn wrote:
> On Sunday, 16 February 2020 at 17:10:24 UTC, Jonathan M Davis
>
> wrote:
> > On Sunday, February 16, 2020 7:29:11 AM MST uranuz via
> >
> >> This is working fine with disabled postblit...
> >> import std;
> >>
> >> struct SS
> >> {
> >>
> >>      @disable this(this); // Disabled copy
> >>
> >>      bool _empty = false;
> >>
> >>      bool empty() @property {
> >>
> >>          return _empty;
> >>
> >>      }
> >>
> >>      void popFront() {
> >>
> >>         _empty = true;
> >>
> >>      }
> >>
> >>      int front() @property { return 10; }
> >>
> >> }
> >>
> >>
> >> void main()
> >> {
> >>
> >>      foreach( it; SS() ) { writeln(it); }
> >>
> >> }
> >>
> >> Am I missing something?
> >
> > That code compiles, because you're passing a temporary to foreach. So, the compiler does a move instead of a copy. It's the difference between
> >
> > auto ss = SS();
> >
> > and
> >
> > SS ss;
> > auto ss2 = ss;
> >
> > If your main were
> >
> >     void main()
> >     {
> >
> >         SS ss;
> >         foreach( it; ss ) { writeln(it); }
> >
> >     }
> >
> > then it would not compile.
>
> On the other hand, this does work:
>
>      void main()
>      {
>          SS ss;
>          foreach( it; move(ss) ) { writeln(it); }
>      }
>
> So perhaps the correct approach is to use `move` when copying input ranges.

Given that the way that almost all range-based functions work is to copy the range that they're given (often then wrapping it in another range that's returned), I don't see how it would make sense to use move outside of very specific circumstances. If you pass a range to a function, and it's a basic input range, then you just use the range via the return value (be it the same range returned directly or returned within a wraper range), and if it's a forward range, you call save before passing it to the function if you want to be able to use the range directly again. Either way, generic code should never be using a range after it's been copied, and copying is a key part of how idiomatic, range-based code works in D.

And really, using move just to be able to use an uncopyable range with foreach doesn't make a lot of sense, since if that's what you want to do, you can always just use a normal for loop. Regardless, there isn't much point in declaring a range type that can't be copied, since it's pretty much only going to work with code that you write.

- Jonathan M Davis



February 16, 2020
On Sunday, 16 February 2020 at 18:11:11 UTC, Jonathan M Davis wrote:
> Either way, generic code should never be using a range after it's been copied, and copying is a key part of how idiomatic, range-based code works in D.

"Copy and then never use the original again" is conceptually the same thing as "move", right? In which case, generic code can accommodate non-copyable ranges *and* more clearly communicate its intent by using `move` instead of a naked copy.
February 16, 2020
On Sunday, February 16, 2020 12:22:01 PM MST Paul Backus via Digitalmars-d- learn wrote:
> On Sunday, 16 February 2020 at 18:11:11 UTC, Jonathan M Davis
>
> wrote:
> > Either way, generic code should never be using a range after it's been copied, and copying is a key part of how idiomatic, range-based code works in D.
>
> "Copy and then never use the original again" is conceptually the same thing as "move", right? In which case, generic code can accommodate non-copyable ranges *and* more clearly communicate its intent by using `move` instead of a naked copy.

We already have enough of a mess with save without making things even worse by trying to add moves into the mix. Also, non-copyable ranges have never really been a thing, and I really don't want to see things complicated even further trying to support such an uncommon use case. There are too many weird corner cases with ranges as it is.

- Jonathan M Davis



February 17, 2020
>> > Either way, generic code should never be using a range after it's been copied, and copying is a key part of how idiomatic, range-based code works in D.

OK. Thanks for instructions. I shall give it a try.

1 2
Next ›   Last »