ideas about ranges

Jason House jason.james.house at gmail.com
Thu May 21 14:40:27 PDT 2009


Steven Schveighoffer Wrote:

> 
> The thread discussing what to do for input ranges vs. forward ranges got  
> me thinking.
> 
> The range concept may be defined backwards in terms of which is more  
> specialized.  Consider that an input range is always usable as a stream.   
> But a stream is not easy to use as an input range (the range primitive).
>
> Case in point, a file.  To fit into the most primitive range concept, it  
> must define 3 functions:
> 
> front()
> popFront()
> empty()
> 
> empty is easy, it's just "am I at end of file"
> 
> But front is not so easy.  In order to know what's at the front, you need  
> to read it.  And at that point you've altered the underlying file.
> 
> Look at the implementation of front and popFront for a possible FileByChar  
> implementation:
> 
> dchar front()
> {
>    if(!bufferValid)
>      popFront();
>    return buffer;
> }
> 
> void popFront()
> {
>     // read buffer from source, setting bufferValid if the range isn't  
> empty by default
> }
> 
> What sucks about this is, we have to introduce a buffer in the range, just  
> because we can't look at data until we've popped it.  Not only that, but  
> calling front a file before anything is read requires a check to fill the  
> buffer in case we haven't read anything yet.  This could be alleviated by  
> filling in the constructor, but it's still more complex than necessary.   
> Consider also that the underlying stream might already be buffered, so we  
> are buffering a buffer.
> 
> And finally, if you copy such a range, the buffer might be copied while  
> the stream itself may not.  this could result in strange garbage data.
> 
> But since the primitives for input range are set by the compiler (it uses  
> them to do foreach), we have to implement them to make our stream ranges  
> friendly to foreach.
> 
> Round peg, meet square hole.
> 
> But what are the true requirements for iteration using foreach?
> 
> 1. check if there's anything left
> 2. get the next element
> 
> Step 2 now is split into popFront and front.  So a foreach loop is a  
> rewritten for loop like this:
> 
> foreach(x; range)
> {
>    ...
> }
> 
> translates to:
> {
>    auto _r = range;
>    while(!_r.empty)
>    {
>      auto x = _r.front();
>      _r.popFront();
>      ...
>    }
> }
> 
> What if step 2 was one function?  Call it popNext(), and make it  
> equivalent to calling _r.front() and popFront() in one step on ranges that  
> implement that method.
> 
> How does this work with foreach?
> 
> {
>    auto _r = range;
>    while(!_r.empty)
>    {
>      auto x = _r.popNext();
>      ...
>    }
> }
> 
> Basically, the same code, one less line.
> 
> Consider that any range defined today with front() and popFront() can  
> implement popNext (and popNext could be an external function if we can get  
> 3015 resolved).
> 
> So what I think we may need is a different range primitive:
> 
> An iterable range defines: (name to be decided)
> 
> bool empty()
> T popNext()
> 
> An input range is an iterable range that also defines:
> 
> T front();
> popFront();

This is exactly like what Andrei posted as his design.

Your iterable range = Andrei's input range
Your input range = Andrei's forward range

 
> Now look at our FileByChar example as an iterable range:
> 
> T popNext()
> {
>     return source.get(); // no buffering required
> }
> 
> And it works perfectly with the new foreach requirements.
> 
> And it correctly doesn't work with algorithms that require front and  
> popFront.
> 
> -Steve




More information about the Digitalmars-d mailing list