May 11, 2021
On Tuesday, 11 May 2021 at 13:50:46 UTC, Andrei Alexandrescu wrote:
> No it isn't.
>
> EnumString and String are reference types. A reference to an enum value does not convert to a reference to its representation. Very very very VERY different.
>

Here we hit at the core of the problem. A reference to a type B that is a subtype of A is not a subtype of ref A. Or, in simlpler terms, B is a subtype of A doesn't imply that ref B is a subtype of ref A.

This means that you can pass a B where an A is expected, but not a ref B where a ref A is expected.

You'll note that the example I provided with classes for understanding will also demonstrate the same behavior:

class A { ... }
class B : A { ... }

void foo(ref A a) { ... }

B b = ...;
foo(b); // This must be an error because, while B is a subtype of A, ref B is not a subtype of ref A.

That means that you shouldn't be able to pass SomeEnumString to any function, in phobos or elsewhere, that will mutate it, such as popFront. But you should be able to do so, transparently, so any function that won't. That includes all compile time parameters.

May 11, 2021
On Tuesday, 11 May 2021 at 13:56:50 UTC, Andrei Alexandrescu wrote:
> Then enum strings are not ranges, correct?

They are not. But they are strings. Which imply that string aren't ranges, which is right, `ref strings` are ranges, not strings.
May 11, 2021

On Tuesday, 11 May 2021 at 13:41:53 UTC, Andrei Alexandrescu wrote:

>

True, D has only "orphan" ranges, no containers. std.container is not working out and with current D technology we can't define containers that work with safe/pure/nogc at the same time (two out of three we can).

How much value does pure have here anyway? Typical container usage involves allocating from the global (!) heap, which arguably should be impure, hacks like pureMalloc notwithstanding.

May 11, 2021
On 5/11/21 10:34 AM, deadalnix wrote:
> On Tuesday, 11 May 2021 at 13:56:50 UTC, Andrei Alexandrescu wrote:
>> Then enum strings are not ranges, correct?
> 
> They are not. But they are strings. Which imply that string aren't ranges, which is right, `ref strings` are ranges, not strings.

`ref string` is not a type.
May 11, 2021
On 5/10/21 5:55 PM, deadalnix wrote:
> On Monday, 10 May 2021 at 13:30:52 UTC, Paul Backus wrote:
>> popFront doesn't return a value, it mutates. So `r` before popFront and `r` after popFront must be the same type, because they are the same variable.
>>
>> If popFront for a string enum is `r = r[1 .. $]`, and typeof(r[1 .. $]) != typeof(r), then it doesn't work, and string enums can't be ranges (from which it follows that they are not Liskov-substitutable for strings).
> 
> r = r[1 .. $] is an error unless r actually is a string. You cannot mutate an enum value and have it stay an enum.
> 
> If you think that invalidate the LSP, I'm afraid there is a big misunderstanding about the LSP. Not all operation on a subtype have to return said subtype. It is made clearer if you consider the slicing operationa s a member function on an object instead - as I seems classes and inheritance is the only way OPP is understood these days.
> 
> class A {
>     A slice(int start, int end) { ... }
> }
> 
> class B : A {}
> 
> Where is it implied that B's version of the slice operation must return an A?

If we move the goalposts we can with certain ease create the illusion that a lot of things are possible and even easy. This works very well in forum discussions where all needed is eloquence and the perseverance to answer every post with one that just slightly moves the discussion around so it appears to have answers to every objection and have the last word on any topic. This is exactly what happens here - half of your points contradict the other half, but never in the same post and the appearance is you seem to have easy answers to everything.

In the initial days of ranges we actually considered that popFront() would be actually tail() that returns by value. So instead of today's form (given a range r):

for (; !r.empty; r.popFront) {
   ... use r.front ...
}

we'd have had:

for (; !r.empty; r = r.tail) {
   ... use r.front ...
}

This doesn't change things much (and wouldn't improve the situation with enums) but does open up the possibility - what if r.tail() actually returns a type different from r?

In all interesting cases that means r = r.tail wouldn't work anymore, which complicates range algorithms A LOT. They'd need to use recursion instead of iteration:

void someRangeFunction(R)(R range) {
    if (range.empty) {
        ... empty case ...
    } else {
        ... do some work for r.front ...
        return someRangeFunction(r.tail);
    }
}

(I should note that that's actually of interest for immutable ranges, for the simple reason they aren't assignable.)

At any rate, we decided this would complicate everything in Phobos way too much (and I think that was a correct prediction) so we chose to have popFront() mutate the current range.

May 11, 2021
On Tuesday, 11 May 2021 at 15:19:05 UTC, Andrei Alexandrescu wrote:
> On 5/11/21 10:34 AM, deadalnix wrote:
>> On Tuesday, 11 May 2021 at 13:56:50 UTC, Andrei Alexandrescu wrote:
>>> Then enum strings are not ranges, correct?
>> 
>> They are not. But they are strings. Which imply that string aren't ranges, which is right, `ref strings` are ranges, not strings.
>
> `ref string` is not a type.

This is just denial.

There are many exemple of conversions that differs with string and ref strings which do not involve enums. For instance, immutable(string) -> string is a valid conversion, but immutable(string) -> ref string isn't.

Call it something else than a type if you want, nevertheless, conversions rules are simply different, even if you abstract the notion of rvalue/lvalue from the whole thing, so it is clearly more than just a regular storage class.

When you say ref, you say "I do not want a subtype". Saying B isn't a subtype of A because I can't pass a B to what expects a ref A is just fallacious.
May 11, 2021
On Tuesday, 11 May 2021 at 15:33:45 UTC, Andrei Alexandrescu wrote:
> If we move the goalposts we can with certain ease create the illusion that a lot of things are possible and even easy.
>
> [...]
>
> At any rate, we decided this would complicate everything in Phobos way too much (and I think that was a correct prediction) so we chose to have popFront() mutate the current range.

I don't think that any of what you wrote is incorrect, and these are even reasonable tradeofs as far as I can tell.

I however would like to remind where this whole thing starts from:

format!SomeEnumString(...) is expected to work for users.

Not that SomeEnumString is a full fledged range or anything, simply that you can pass is down to phobos, or anything else for that matter, in place where a string is expected.

This is reasonable expectation.

It is also a reasonable expectation that this shouldn't require a ton of scaffolding to work, in phobos or elsewhere.

Therefore, the fact that phobos required scaffolding to make this work is indicative that there is a deeper problem. Focusing on finding what that deeper problem is and fixing it seems like a healthier path forward than simply pretending there is no problem and pushing it all on the users.

I this case, it was noted here ( https://forum.dlang.org/post/umndraexmrxiyrmfpcyo@forum.dlang.org ) that the root cause of the problem might be that there is a conflation between the container and the range. I think this is a reasonable hypothesis. Having two things trying to do one thing is a very typical source of such problems.
May 11, 2021
On 5/11/21 12:13 PM, deadalnix wrote:
> On Tuesday, 11 May 2021 at 15:19:05 UTC, Andrei Alexandrescu wrote:
>> On 5/11/21 10:34 AM, deadalnix wrote:
>>> On Tuesday, 11 May 2021 at 13:56:50 UTC, Andrei Alexandrescu wrote:
>>>> Then enum strings are not ranges, correct?
>>>
>>> They are not. But they are strings. Which imply that string aren't ranges, which is right, `ref strings` are ranges, not strings.
>>
>> `ref string` is not a type.
> 
> This is just denial.

It's simple fact.

> There are many exemple of conversions that differs with string and ref strings which do not involve enums. For instance, immutable(string) -> string is a valid conversion, but immutable(string) -> ref string isn't.
> 
> Call it something else than a type if you want, nevertheless, conversions rules are simply different, even if you abstract the notion of rvalue/lvalue from the whole thing, so it is clearly more than just a regular storage class.
> 
> When you say ref, you say "I do not want a subtype". Saying B isn't a subtype of A because I can't pass a B to what expects a ref A is just fallacious.

Again with moving the goalposts.
May 11, 2021
On 5/11/21 12:39 PM, Andrei Alexandrescu wrote:
> On 5/11/21 12:13 PM, deadalnix wrote:
>> On Tuesday, 11 May 2021 at 15:19:05 UTC, Andrei Alexandrescu wrote:
>>> On 5/11/21 10:34 AM, deadalnix wrote:
>>>> On Tuesday, 11 May 2021 at 13:56:50 UTC, Andrei Alexandrescu wrote:
>>>>> Then enum strings are not ranges, correct?
>>>>
>>>> They are not. But they are strings. Which imply that string aren't ranges, which is right, `ref strings` are ranges, not strings.
>>>
>>> `ref string` is not a type.
>>
>> This is just denial.
> 
> It's simple fact.
> 
>> There are many exemple of conversions that differs with string and ref strings which do not involve enums. For instance, immutable(string) -> string is a valid conversion, but immutable(string) -> ref string isn't.
>>
>> Call it something else than a type if you want, nevertheless, conversions rules are simply different, even if you abstract the notion of rvalue/lvalue from the whole thing, so it is clearly more than just a regular storage class.
>>
>> When you say ref, you say "I do not want a subtype". Saying B isn't a subtype of A because I can't pass a B to what expects a ref A is just fallacious.
> 
> Again with moving the goalposts.

To clarify: you can't make up your own definitions as you go so as to support the point you're making at the moment. You can't go "oh, call it something else than a type, my point stays". No. Your point doesn't stay.

By the same token you can't make up your own definition of what subtyping is and isn't. Value types and reference types are well-trodden ground. You can't just claim new terminology and then prove your own point by using it.
May 11, 2021
On 5/11/21 12:26 PM, deadalnix wrote:
> I however would like to remind where this whole thing starts from:
> 
> format!SomeEnumString(...) is expected to work for users.

Reasonable, though I should add that it's a decision made by the author of the format() API.

> Not that SomeEnumString is a full fledged range or anything, simply that you can pass is down to phobos, or anything else for that matter, in place where a string is expected.

Reasonable, though again a matter of API definition. Would you expect this to work?

float sin(float x);
double sin(double x);
real sin(real x);
...
auto x = sin(1);

Shouldn't that work? Not that int is a full fledged floating point number or anything, simply that you can pass it down to phobos, or anything else for that matter, in place where a floating point number is expected.

Oh, but wait, it's the templates. Great.

T sin(T)(T x) if (isFloatingPoint!T);
...
auto x = sin(1);

Shouldn't that work? Not that int is a full fledged floating point number or anything, simply that you can pass it down to phobos, or anything else for that matter, in place where a floating point number is expected.

Well an argument can be made that it should work, or the API designer can wisely choose to NOT yield true from isFloatingPoint!int.

And if we explore this madness further, we get to an enormity just as awful as StringTypeOf:

template FloatingPointTypeOf(T) {
    static if (isIntegral!T) {
        alias FloatingPointTypeOf = T;
    } else ...
}

And then whenever we need a floating point type we use is(FloatingPointTypeOf!T) like a bunch of dimwits.

What use case does that helps? Who is helped by that? Someone who can't bring themselves to convert whatever they have to double prior to using the standard library.

Arguably not a good design.