Adapting foreign iterators to D ranges

2024-04-22 Thread Chloé via Digitalmars-d-learn

Assume a third-party API of the following signature:

T* next(I iter);

which advances an iterator of sorts and returns the next element, or 
null when iteration is done. No other information about the state of the 
iterator is available.


I wish to adapt this interface to a forward range for use with foreach 
and Phobos' range utilities. This amounts to implementing empty, front, 
and popFront, in terms of next and some state. But there is a choice to 
be made regarding the first call to next.


One could call next during range construction:

struct Range
{
private I iter;
private T* current;
this(I iter) { this.iter = iter; current = next(iter); }
bool empty() const => current is null;
inout(T)* front() inout => current;
void popFront() { current = next(iter); }
}

Or do not call it until the first call to empty:

struct Range
{
private bool initialized;
private I iter;
private T* current;
this(I iter) { this.iter = iter; }
bool empty()
{
if (!initialized) {
current = next(iter);
initialized = true;
}
return current is null;
}
inout(T)* front() inout => current;
void popFront() { current = next(iter); }
}

The first implementation has the advantage is being simpler and empty 
being const, but has the downside that next is called even if the range 
ends up not being used. Is either approach used consistently across the 
D ecosystem?


Re: Adapting foreign iterators to D ranges

2024-04-22 Thread Alexandru Ermicioi via Digitalmars-d-learn

On Monday, 22 April 2024 at 11:36:43 UTC, Chloé wrote:
The first implementation has the advantage is being simpler and 
empty being const, but has the downside that next is called 
even if the range ends up not being used. Is either approach 
used consistently across the D ecosystem?


You can also place initialization logic inside front, then empty 
could become const.


I don't think there is a preffered way of initializing such 
ranges, so imho consider what's best for your use case.




Re: Adapting foreign iterators to D ranges

2024-04-22 Thread Steven Schveighoffer via Digitalmars-d-learn

On Monday, 22 April 2024 at 11:36:43 UTC, Chloé wrote:

The first implementation has the advantage is being simpler and 
empty being const, but has the downside that next is called 
even if the range ends up not being used. Is either approach 
used consistently across the D ecosystem?


I always go for the simplest approach. So that means, pre-fill in 
the constructor.


Yes, the downside is, if you don't use it, then the iterator has 
moved, but the range hasn't. But returning to the iterator after 
using the range is always a dicey proposition anyway.


The huge benefit is that all the functions become simple and 
straightforward.


But there is no "right" approach. And using composition, you may 
be able to achieve all approaches with wrappers. Phobos does 
various things depending on what people thought was good at the 
time. It sometimes causes some very unexpected behavior.


I recommend always using the same approach for the same library, 
that way your users know what to expect!


-Steve


Re: Adapting foreign iterators to D ranges

2024-04-22 Thread cc via Digitalmars-d-learn

On Monday, 22 April 2024 at 11:36:43 UTC, Chloé wrote:
I wish to adapt this interface to a forward range for use with 
foreach and Phobos' range utilities. This amounts to 
implementing empty, front, and popFront, in terms of next and 
some state. But there is a choice to be made regarding the 
first call to next.


Just to offer an alternative solution (since it sometimes gets 
overlooked), there is also the `opApply` approach.  You don't get 
full forward range status, and checking whether it's empty 
essentially requires doing something like std.algorithm 
`walkLength`, but if all you need is basic iteration, it can be a 
simpler solution:


```d
struct Range {
private I iter;
this(I iter) { this.iter = iter; }
int opApply(scope int delegate(T* t) dg) {
while (auto current = next(iter)) {
if (auto r = dg(current))
return r;
}
return 0;
}
}
void main() {
I someIter; // = ...
auto range = Range(someIter);
foreach (const t; range) {
writeln(*t);
}
}
```