std.v2020.algorithm etc[ WAS: Is run.d going to be expand for runtime and the phobos library?]

Stanislav Blinov stanislav.blinov at gmail.com
Sat Jun 27 13:11:09 UTC 2020


On Saturday, 27 June 2020 at 12:25:34 UTC, Joseph Rushton 
Wakeling wrote:

> But -- for example -- what would it mean to have an input range 
> as part of a const or immutable data structure?  Forget the 
> strict interpretation of the current D keywords, let's just 
> think conceptually for a moment.  Do we want to think of an 
> immutable input range as just a source of input, with any 
> under-the-hood mutability just an implementation detail that 
> shouldn't be of interest to the external caller?  Do we 
> consider it a contradiction in terms?

An immutable or const input range just cannot be. An input range 
can't not be mutable.
If a range itself can implement a `tail()`, it's not an input 
range, it's a forward range. The only way for `tail()` to work 
with input ranges is to be a free function:

R tail(R)(R r)
if (isInputRangeWithAHypotheticalAPI!R)
{
     r.next(); // fetchNext(), advance(), whatever the API is
     return move(r);
}

and used like this:

input = move(input).tail;

(Yes, I'm still insisting that input ranges must be non-copyable).

> I also think it may be worth making a point of comparison to 
> Rust's iterators.  Obviously Rust has a different approach to 
> immutability than D -- the main concern is preventing multiple 
> mutable references to the same piece of data -- but there is a 
> very clear separation between iterators versus what they 
> iterate over, and generally speaking (probably because of that 
> separation) iterators themselves seem to be always implemented 
> as mutable.

True input ranges are basically Rust's iterators, i.e. their API 
should be

Optional!T next(); // in other words, a bool and a T

or variations thereof, as outlined by Andrei some pages back. 
IMHO, the above API is the cleanest. But there is also place for 
buffered input ranges (i.e. the currently existing API), see 
below.

> So, what I'm interested in is whether we can reliably rework 
> the input range API such that the only way to determine the 
> next element is to actually fetch it.

Both APIs have their place. A "true" input range (i.e. a 
generator) yields temporaries, and only needs a "fetch next" 
primitive. But the *algorithms* may require a buffer. Consider a 
`find`: it needs to return the needle and the remainder of the 
range. In order to find the needle it needs to pop it off from 
the "fetch next" haystack, irreversibly advancing it. If we want 
to keep `find` generic over input and forward ranges (i.e. 
returning a range), it will have to wrap the haystack into a 
buffered input range with the front/popFront API, placing the 
found needle at the front, i.e. hypothetically:

auto find(alias pred, Range, Needle)(Range range, auto ref const 
Needle needle)
if (isInputRangeWithAHypotheticalAPI!R)
{
     static struct Result
     {
         private typeof(Range.init.next()) buf;
         private Range src;

         // So long as the Optional holds a valid value, range is 
not empty
         @property bool empty() const { return !buf; }

         // Accessing an empty Optional would be illegal,
         // so the non-empty() invariant is observed.
         ref front() inout { return buf.value; }

         void popFront() { buf = src.next(); }
     }

     typeof(range.next()) current; // i.e. an Optional!ElementType
     while (true)
     {
         current = range.next;
         if (!current || pred(current.value, needle))
             break;
     }

     return Result(move(current), move(range));
}


More information about the Digitalmars-d mailing list