September 10, 2008
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:ga5r8i$h0v$1@digitalmars.com...
> So in essence the behavior is that you can use isEmpty to
> make sure that getNext won't blow in your face (speaking of Pulp
> Fiction...)

So isEmpty is optional for input ranges? This does not actually match your own documentation:

>getNext: The call is defined only right after r.isEmpty returned false.

If you make isEmpty optional, its non-constness is less of a problem. What I have a problem with (overstatement) is having to call isEmpty to actually prepare the next element. If try { while (1) e = ir.getNext; } works, I'm sold : )

> r.isEmpty does whatever the hell it takes to make sure whether there's
> data available or not. It is not const and it could throw an exception.
>
> v = r.getNext returns BY VALUE by means of DESTRUCTIVE COPY data that
> came through the wire, data that the client now owns as soon as getNext
> returned. There is no extra copy, no extra allocation, and the real
> thing has happened: data has been read from the outside and user code
> was made the only owner of it.

Thank you for taking the time to explain all these details. This is all great stuff.

L. 

September 10, 2008
"superdan" <super@dan.org> wrote in message news:ga5vjs$snn$1@digitalmars.com...
> Andrei Alexandrescu Wrote:
>
>> What we want is a design that tells the truth. And a design that tells
>> the truth is this:
>>
>> r.isEmpty does whatever the hell it takes to make sure whether there's
>> data available or not. It is not const and it could throw an exception.
>>
>> v = r.getNext returns BY VALUE by means of DESTRUCTIVE COPY data that
>> came through the wire, data that the client now owns as soon as getNext
>> returned. There is no extra copy, no extra allocation, and the real
>> thing has happened: data has been read from the outside and user code
>> was made the only owner of it.
>
> this is really kool n the gang. there's a sore point tho. if i wanna read strings from a file no prob.
>
> for (auto r = stringSucker(stdin); !r.isEmpty(); )
> {
>    string s = r.getNext();
>    // play with s
> }
>
> but a new string is allocated every line. that's safe but slow. so i want some more efficient stuff. i should use char[] because string don't deallocate.
>
> for (auto r = charArraySucker(stdin); !r.isEmpty(); )
> {
>    char[] s = r.getNext();
>    // play with s
> }
>
> no improvement. same thing a new char[] is alloc each time. maybe i could do
>
> for (auto r = charArraySucker(stdin); !r.isEmpty(); )
> {
>    char[] s = r.getNext();
>    // play with s
>    delete s;
> }
>
> would this make stuff faster. maybe. maybe not. and it's not general. what i'd like is some way of telling the range, i'm done with this you can recycle and reuse it. it's a green green world.
>
> for (auto r = charArraySucker(stdin); !r.isEmpty(); )
> {
>    char[] s = r.getNext();
>    // play with s
>    r.recycle(s);
> }
>
> sig is recycle(ref ElementType!(R)).

Can't this be done by creating different ranges? I mean, trying to find a 'one size fits all' model is usually a lost cause. And now we have different consumers and different types.

Perhaps one sucker is instantiated with its own internal buffer and another sucker allocates every new item. And possibly yet another sucker only returns invariant items.

L.

September 10, 2008
Bill Baxter wrote:
> On Wed, Sep 10, 2008 at 8:04 AM, Andrei Alexandrescu
> <SeeWebsiteForEmail@erdani.org> wrote:
> 
>> Finally the coin dropped on the Arabic/Hebrew cultural thing. I don't think
>> they'd be offended. This is not writing. Left is left and right is right in
>> math.
> 
> Also the direction in which D code is written does not depend on the
> language of the speaker.  It's always left to right.
> So I think there's no real argument on linguistic grounds.
> 
> On the other hand, a quick google for "left right confusion" turns up
> a fair number of relevant hits.

Yep. I needn't google any farther than my wife :o).

Andrei
September 10, 2008
Lionello Lunesu wrote:
> 
> "Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:ga5r8i$h0v$1@digitalmars.com...
>> So in essence the behavior is that you can use isEmpty to
>> make sure that getNext won't blow in your face (speaking of Pulp
>> Fiction...)
> 
> So isEmpty is optional for input ranges? This does not actually match your own documentation:
> 
>> getNext: The call is defined only right after r.isEmpty returned false.
> 
> If you make isEmpty optional, its non-constness is less of a problem. What I have a problem with (overstatement) is having to call isEmpty to actually prepare the next element. If try { while (1) e = ir.getNext; } works, I'm sold : )

I think I'd want to make it nonoptional such that people wanting real fast iterators can define r.isEmpty to do a check and r.getNext (well, r.left) to go unchecked.

>> r.isEmpty does whatever the hell it takes to make sure whether there's
>> data available or not. It is not const and it could throw an exception.
>>
>> v = r.getNext returns BY VALUE by means of DESTRUCTIVE COPY data that
>> came through the wire, data that the client now owns as soon as getNext
>> returned. There is no extra copy, no extra allocation, and the real
>> thing has happened: data has been read from the outside and user code
>> was made the only owner of it.
> 
> Thank you for taking the time to explain all these details. This is all great stuff.

However, superdan destroyed me. (See my answer to him.) I think I need to concede to your design.

Andrei
September 10, 2008
Lionello Lunesu wrote:
> 
> "superdan" <super@dan.org> wrote in message news:ga5vjs$snn$1@digitalmars.com...
>> Andrei Alexandrescu Wrote:
>>
>>> What we want is a design that tells the truth. And a design that tells the truth is this:
>>>
>>> r.isEmpty does whatever the hell it takes to make sure whether there's data available or not. It is not const and it could throw an exception.
>>>
>>> v = r.getNext returns BY VALUE by means of DESTRUCTIVE COPY data that came through the wire, data that the client now owns as soon as getNext returned. There is no extra copy, no extra allocation, and the real thing has happened: data has been read from the outside and user code was made the only owner of it.
>>
>> this is really kool n the gang. there's a sore point tho. if i wanna read strings from a file no prob.
>>
>> for (auto r = stringSucker(stdin); !r.isEmpty(); )
>> {
>>    string s = r.getNext();
>>    // play with s
>> }
>>
>> but a new string is allocated every line. that's safe but slow. so i want some more efficient stuff. i should use char[] because string don't deallocate.
>>
>> for (auto r = charArraySucker(stdin); !r.isEmpty(); )
>> {
>>    char[] s = r.getNext();
>>    // play with s
>> }
>>
>> no improvement. same thing a new char[] is alloc each time. maybe i could do
>>
>> for (auto r = charArraySucker(stdin); !r.isEmpty(); )
>> {
>>    char[] s = r.getNext();
>>    // play with s
>>    delete s;
>> }
>>
>> would this make stuff faster. maybe. maybe not. and it's not general. what i'd like is some way of telling the range, i'm done with this you can recycle and reuse it. it's a green green world.
>>
>> for (auto r = charArraySucker(stdin); !r.isEmpty(); )
>> {
>>    char[] s = r.getNext();
>>    // play with s
>>    r.recycle(s);
>> }
>>
>> sig is recycle(ref ElementType!(R)).
> 
> Can't this be done by creating different ranges? I mean, trying to find a 'one size fits all' model is usually a lost cause. And now we have different consumers and different types.
> 
> Perhaps one sucker is instantiated with its own internal buffer and another sucker allocates every new item. And possibly yet another sucker only returns invariant items.

I don't mind implementing different suckers. :o) The problem is that the suckers won't have the same interface, so some of them will work badly or not at all with std.algorithm.

So again: I think the design you suggested isEmpty/first/getNext is the better one even for input iterators.

Andrei
September 10, 2008
Andrei Alexandrescu wrote:
> I hear you. I brought up the same exact design briefly with Bartosz last week. We called it T.invalid. He argued in favor of it. I thought it brings more complication than it's worth and was willing to go with T.init for simplicity's sake. Why deal with two empty states instead of one.
> 
> One nagging question is, what is T.fail for integral types? For pointers fine, one could be found. For chars, fine too. But for integrals I'm not sure that e.g. T.min or T.max is a credible fail value.

The T.init value should be that. That's why, for floats, float.init is a NaN. But for many types, there is no such thing as an invalid value, so it really doesn't work for generic code.
September 10, 2008
Walter Bright wrote:
> Andrei Alexandrescu wrote:
>> I hear you. I brought up the same exact design briefly with Bartosz last week. We called it T.invalid. He argued in favor of it. I thought it brings more complication than it's worth and was willing to go with T.init for simplicity's sake. Why deal with two empty states instead of one.
>>
>> One nagging question is, what is T.fail for integral types? For pointers fine, one could be found. For chars, fine too. But for integrals I'm not sure that e.g. T.min or T.max is a credible fail value.
> 
> The T.init value should be that. That's why, for floats, float.init is a NaN. But for many types, there is no such thing as an invalid value, so it really doesn't work for generic code.

I don't think values necessarily have to be initialized to an invalid value. You could certainly argue that NaN values are valid results of certain computations, and that they're valid in certain contexts.

The important thing is that they're *uncommon*, and if you see them cropping up all over the place where they shouldn't, you know you have an initialization problem somewhere in your code.

The same thing could be true for integers, but zero is such a common value that it's tough to spot the origin of the error.

If signed integers were initialized to min_value and signed integers were initialized to max_value, I think those initialization errors would be easier to track down. Not because the values are illegal, but because they're *uncommon*.

--benji
September 10, 2008
Dejan Lekic wrote:

> r.fromBegin(s) is really s.toEnd(r)

Might be true only, if `s' equals `r'.

Otherwise at least one seems to be undefined, because not both can be true subranges of each other.

I used the conjunctive form, because a formal definition of "subrange" is missing. Such is needed because in a cyclic model `s' and `r' might be equal, but not identical, because they contain a whole cycle, but their start points differ.

-manfred

-- 
If life is going to exist in this Universe, then the one thing it cannot afford to have is a sense of proportion. (Douglas Adams)

September 10, 2008
On Wed, Sep 10, 2008 at 12:46 PM, Manfred_Nowak <svv1999@hotmail.com> wrote:
> Dejan Lekic wrote:
>
>> r.fromBegin(s) is really s.toEnd(r)
>
> Might be true only, if `s' equals `r'.
>
> Otherwise at least one seems to be undefined, because not both can be true subranges of each other.
>
> I used the conjunctive form, because a formal definition of "subrange" is missing. Such is needed because in a cyclic model `s' and `r' might be equal, but not identical, because they contain a whole cycle, but their start points differ.

So I think you can put that in other words by saying that they could mean different things if there's more to a range's state than just a beginning indicator and an end indicator.

For instance in your example it would be like putting a "#of cycles" member in the range itself as a third element, rather than associating it with the end marker.

Interesting.  I suppose that sort of thing is possible, but maybe such possibilities are annoying enough that they should be made illegal. In your example it seems simple enough to make the cycle count a property associated with the "end", and then the difference goes away.

--bb
September 10, 2008
Hi Andrei,

I like the idea behind ranges.  I don't like C++'s / stl's long winded syntax at all.  Its so large that it generally uses up several lines along with several typedefs etc...  All that work just to iterate over some data.  The longer things get the more error prone they get... how many times have I put an begin when I meant to put end *sigh*.

However I currently disagree on this point.

Andrei Alexandrescu wrote:
>
> Fine. So instead of saying:
>
> foreach (e; c.all) { ... }
>
> you can say
>
> foreach (e; c) { ... }
>
> I think that's some dubious savings.


I think its useful to have the implicit range conversion.  Consider writing generic/template code.  Of course built in arrays could provide the .all but then consider passing around ranges.  That would also mean all ranges would also have a .all (could we go .all.all.all for instance?).  I'm all for compile time checking however I think that implicit .all (with of course an explicit option) will make it easy to change a function that once took an object to take a simple range  Also it would make it easy to change from one way of getting at a range to another.

What about matrices?  They don't implement default .all, they would provide like .col and .row.

> Andrei