opNext: Simplified ranges

Tomer at Weka Tomer at Weka
Sat Nov 5 06:01:54 UTC 2022


I find the empty/front/popNext protocol very cumbersome to 
implement for most cases (at least in the Weka codebase), which 
causes people to just prefer opApply (even though it has its own 
set of issues).

Today I even ran into a problem that cannot be solved using the 
existing protocol: a consuming range (think a file stream) that 
should not consume the element if the foreach has been broken. 
Using opApply, this is easy (the delegate returns nonzero), but 
using popNext it's next to impossible (or at least really really 
cumbersome). I don't want to go into all the details, so let's 
just jump to the suggested protocol.

```
bool opNext(out T elem) {...}
```

`opNext` returns true if it "produced" an element, false if it 
reached the end. That way

```
foreach (elem; range) {
     ...
}
```

Simply gets lowered to
```
T elem;
while (range.opNext(elem)) {
    ...
}
```

Of course this protocol can live side-by-side with the existing 
protocol, just take precedence in the lowering rules. A concrete 
example:

```
struct LinkedList {
     int value;
     LinkedList* next;

     struct Range {
         LinkedList* cursor;

         bool opNext(out int val) nothrow @nogc {
             if (cursor is null) {
                 return false;
             }
             val = cursor.value;
             return true;
         }
     }

     auto members() {return Range(&this);}
}

foreach (val; myList.members) {
     ...
}
```

Notes:
* `save()` can work for the new protocol as well, it just needs 
to duplicate the iterator's state
* Maybe it would be wise to complement this with opPrev for 
bidirectional ranges, but I have to say I've rarely seen any use 
of them
* I think chaining with map/filter/etc. is even easier to 
implement in the new protocol



More information about the Digitalmars-d mailing list