January 22
On 21/01/2024 11:58 PM, Jonathan M Davis wrote:
> Either way, if we're doing a new major version of Phobos, we'll be reworking
> a variety of stuff anyway, and so Nullable would presumably be on the list
> (e.g. I think that it was a big mistake to make it a range, and I'd remove
> that functionality; slicing it to get a range is one thing, but the fact
> that it itself is a range definitely causes problems in generic code).

Umm, why would we bring back Nullable?

By then we'd have sum types which can represent it in the language far better.
January 21
On Sunday, 21 January 2024 at 14:51:33 UTC, Paul Backus wrote:
>
> With the proposed design, it would be possible to implement next() for forward ranges as a UFCS function:
>
>     auto next(FR)(FR fr)

Correction: fr should be passed by ref (or maybe auto ref).
January 21
On Sunday, 21 January 2024 at 17:15:58 UTC, Walter Bright wrote:
> Another thing that should be visited is the ability to lock/unlock a range. For example, a range that represents stdout needs to be lockable.

Surely this would be better handled outside the range API.

E.g., you call a library function that locks stdout and returns a range, and then when you're done with the range, it unlocks stdout again in its destructor. But the functions you pass the range to (map, filter, whatever) don't need to know anything about locking.

This *would* be a good use-case for a non-copyable range, since you only want to unlock once (and reference counting would add significant overhead). So even if we don't *require* ranges to be non-copyable, we probably want to *allow* then to be.
January 21
On Sunday, 21 January 2024 at 13:48:56 UTC, Jonathan M Davis wrote:
> And how on earth would you use a range if it can't be copied? They're copied all over the place and have to be to even be used. You copy them when you pass them to any range-based function. You copy them when you return them from a range-based function. You copy them when they get wrapped by another range. You copy them when you pass them to foreach. Disabling copying on ranges would make them borderline useless.

Maybe they could be moved instead of copied when the original is not needed.

> And you also can't disable copying on classes or dynamic arrays.

Unless all ranges have to be structs, but then we probably need a function/syntax to ask for a range from a class or dynamic array.
January 21
On Sunday, 21 January 2024 at 05:00:31 UTC, Jonathan M Davis wrote:
> I've been thinking about this for a while now, but with the next version of Phobos which is in the early planning stages, we really should do some redesigning of ranges.

Thanks for the write up.

Another issue I have encountered is around priming. Sometimes it happens in `empty`, sometimes in a private helper function and sometimes even in the constructor.

If priming happens in `empty`, then it can't be `const`. Which is strange because you would expect `empty` not to mutate.

I have been thinking about having an explicit build and iteration phase, where priming happens when you switch from build to iteration.

The benefit is that implementers have a clear place where to prime the range.

> What I would propose for that would be a single function
>
>    auto next();
>
> where next returns a nullable type where the value returned is the next element in the range, with a null value being returned if the range is empty.

What about:

```
    bool next(Callable)(Callable c) {
        if (empty)
            return false;
        c(front());
        popFront();
        return true;
    }
```

It has the benefit of not needing to unbox a Nullable/Pointer.
January 21

On Sunday, 21 January 2024 at 05:00:31 UTC, Jonathan M Davis wrote:

>

Ultimately, I'm inclined to argue that we should give basic input ranges a new API - not just because it would allow them to use reference counting, but also because the current input range API tends to force basic input ranges to cache the value of front (which if anything, encourages basic input ranges to be pseudo-reference types and could result in an extra layer of indirection in some cases if they're forced to be reference types). It would be annoying in some cases to require that different functions be used for basic input ranges and forward ranges (though overloading could obviously be used to avoid needing different names), but it's already often the case that code isn't really designed to work with both, and overloading on the category of range being used is already fairly common, since different range capabilities allow for different implementations. So, given that it would prevent whole classes of copying bugs as well as potentially remove the requirement to cache front for basic input ranges, I think that a separate API for basic input ranges is warranted. What I would propose for that would be a single function

auto next();

where next returns a nullable type where the value returned is the next element in the range, with a null value being returned if the range is empty. The return type would then need to emulate a pointer - specifically, when casting it to bool, it would be true if it's non-null and false if it's null, and dereferencing it would give you the actual value if it's non-null (with it being undefined behavior if you dereference null). So, a basic input range of ints might define next as

int* next();

While I agree with most of what you wrote, there is one very big problem with this. Namely, current input ranges are suddenly considered to be forward ranges. Changing the semantics of what it means to only define front, popFront, and empty changes the semantics of existing code.

And this is not something you can solve with some kind of versioning. The only way I can think of is to alter the name of one or more primitives to prevent accidental usage.

-Steve

January 21
On Sunday, 21 January 2024 at 18:26:37 UTC, Sebastiaan Koppe wrote:
> What about:
>
>     bool next(Callable)(Callable c) {
>         if (empty)
>             return false;
>         c(front());
>         popFront();
>         return true;
>     }
>
> It has the benefit of not needing to unbox a Nullable/Pointer.

If your concern is performance, an indirect function call is going to hurt a lot more than a pointer dereference.

If your concern is aesthetics, dereferencing a pointer or unboxing a Nullable is far less ugly than passing a callback.

    // With pointer
    while (auto p = r.next)
        doStuffWith(*p);

    // With callback
    while (r.next((e) { doStuffWith(e); })) {}
January 21

On Sunday, 21 January 2024 at 19:24:20 UTC, Steven Schveighoffer wrote:

>

While I agree with most of what you wrote, there is one very big problem with this. Namely, current input ranges are suddenly considered to be forward ranges. Changing the semantics of what it means to only define front, popFront, and empty changes the semantics of existing code.

And this is not something you can solve with some kind of versioning. The only way I can think of is to alter the name of one or more primitives to prevent accidental usage.

One possibility is to change the forward range interface to something like

bool empty();
Element head();
typeof(this) tail();

In addition to distinguishing new-style forward ranges from old-style ones, this interface would also allow forward ranges to be const (although iterating a const forward range would only be possible with recursion).

popFront could then be implemented as a UFCS function:

void popFront(R)(ref R r)
    if (std.v2.range.isForwardRange!R && isAssignable!R)
{
    r = r.tail;
}
January 21
On Sunday, January 21, 2024 10:39:41 AM MST Richard (Rikki) Andrew Cattermole via Digitalmars-d wrote:
> On 21/01/2024 11:58 PM, Jonathan M Davis wrote:
> > Either way, if we're doing a new major version of Phobos, we'll be reworking a variety of stuff anyway, and so Nullable would presumably be on the list (e.g. I think that it was a big mistake to make it a range, and I'd remove that functionality; slicing it to get a range is one thing, but the fact that it itself is a range definitely causes problems in generic code).
> Umm, why would we bring back Nullable?
>
> By then we'd have sum types which can represent it in the language far better.

Much as I know that you like the idea of sum types, AFAIK, it's never been officially agreed that we're going to have them in the language, and even if we do get them, I don't know what that's actually going to look like. If it's anything like std.sumtype, IMHO, it would be utterly terrible for when you want a Nullable type. For that, you want to be able to ask whether the object has a value, and if it does, you want to be able to fetch its value. So, while you could use different function names for it, you basically want the same kind of API that a pointer has. Something where you'd have to pass delegates in just to get the value out would be incredibly annoying and bloated in comparison (and personally, I hate std.sumtype in general because of that design, but it makes a lot more sense when you have a variety of possible types as opposed to just one which may or may not be there).

But regardless of whether sum types would make sense in this case (which I very much question), we'd have to know that they were definitely being added to the language and what exactly that would look like - and they would have to function in a way that was clearly superior to have a dedicated Nullable type, or it would make more sense to add a Nullable type and use that for cases where you want a nullable object.

Obviously, if sum types get added to the language soon enough, and they really do replace the need for Nullable, then Nullable probably won't be in Phobos v3, but as thing stand, I would fully expect it to be there. Either way, using the API of a pointer makes a lot of sense for the proposed basic input range API, in which case, a range that wanted to return something other than a pointer would need to return a type that used the same API.

- Jonathan M Davis



January 21
On Sunday, January 21, 2024 7:51:33 AM MST Paul Backus via Digitalmars-d wrote:
> On Sunday, 21 January 2024 at 05:00:31 UTC, Jonathan M Davis
>
> wrote:
> > From what I can see, the main negative is simply that you can't then write code that works on both a basic input range and a forward range (though you can obviously still create function overloads so that the caller can use either)
>
> With the proposed design, it would be possible to implement next() for forward ranges as a UFCS function:
>
>      auto next(FR)(FR fr)
>          if (std.v2.range.isForwardRange!FR)
>      {
>          if (fr.empty)
>          {
>              return Nullable!(ElementType!FR).init;
>          }
>          else
>          {
>              scope(success) fr.popFront;
>              return nullable(fr.front);
>          }
>      }
>
> So, a function written to operate on basic input ranges would be able to accept any kind of range, same as today.

True, but then you have a function with the same copy semantics problem that we have now, since the forward range wouldn't have reference semantics. It would need to be fully wrapped in another type which gave it reference semantics to do that.

So, while someone could do something like this, I question that we should encourage it or support it with anything in Phobos.

- Jonathan M Davis