May 10, 2021
On 5/9/21 5:04 AM, Walter Bright wrote:
> On 5/7/2021 7:05 PM, Andrei Alexandrescu wrote:
>> String s;
>> func1(s.bytes);
>> func2(s.dchars);
> 
> Already done:
> 
> s.byCodeUnit
> s.byChar
> s.byWchar
> s.byDchar
> s.byUTF
> 
> https://dlang.org/phobos/std_utf.html

Problem being of course that there's no UDT String type, only the crappy immutable(char)[].
May 10, 2021
On 5/9/21 8:57 PM, deadalnix wrote:
> On Friday, 7 May 2021 at 16:43:20 UTC, Andrei Alexandrescu wrote:
>> Well you see here is the problem. An enum with base string can be coerced to a string, but is not a true subtype of string. This came to a head with ranges, too - you can pop off the head of a string still have a string, but if you pop off the head of an enum string you get some enum value that is not present in the set of enum values. Concatenation has similar problems, e.g. s ~ s for enum strings yields string, not an enum string. (Weirdly s ~= s works...)
>>
> 
> Popping the head out of an enum value ought to be a string, not that enum's value. I don't really see where the problem is here, this is subtyping 101.

So you have a range r of type T.

You call r.popFront().

Obvioulsly the type of r should stay the same because in D variables don't change type.

So... what gives, young Padawan?

No, this is not subtyping 101.
May 10, 2021
On Monday, 10 May 2021 at 04:21:34 UTC, Andrei Alexandrescu wrote:
> So you have a range r of type T.
>
> You call r.popFront().
>
> Obvioulsly the type of r should stay the same because in D variables don't change type.
>
> So... what gives, young Padawan?
>
> No, this is not subtyping 101.

If you have a range of T, then you got to return a T. I'm not sure what's the problem is here. Do you have a concrete example?

All I can think of are things like slicing and alike, and they should obviously return a string, not a T.
May 10, 2021
On Monday, 10 May 2021 at 12:19:07 UTC, deadalnix wrote:
> On Monday, 10 May 2021 at 04:21:34 UTC, Andrei Alexandrescu wrote:
>> So you have a range r of type T.
>>
>> You call r.popFront().
>>
>> Obvioulsly the type of r should stay the same because in D variables don't change type.
>>
>> So... what gives, young Padawan?
>>
>> No, this is not subtyping 101.
>
> If you have a range of T, then you got to return a T. I'm not sure what's the problem is here. Do you have a concrete example?
>
> All I can think of are things like slicing and alike, and they should obviously return a string, not a T.

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).
May 10, 2021
On Monday, 10 May 2021 at 04:21:34 UTC, Andrei Alexandrescu wrote:
>> Popping the head out of an enum value ought to be a string, not that enum's value. I don't really see where the problem is here, this is subtyping 101.
>
> So you have a range r of type T.
>
> You call r.popFront().
>
> Obvioulsly the type of r should stay the same because in D variables don't change type.
>
> So... what gives, young Padawan?
>
> No, this is not subtyping 101.

This feels a bit like the real problem might be in the conflation of the container (the enum or the string) and the range?

Cf. the way this is handled in Rust, where there is a clear distinction between a container, versus an iterator over that container:
https://doc.rust-lang.org/rust-by-example/flow_control/for.html

Note also the different ways that the iterator can be generated: either using a reference to the container itself, or by moving the container into the iterator so the container itself is consumed by the iteration.
May 10, 2021
On Monday, 10 May 2021 at 17:09:37 UTC, Joseph Rushton Wakeling wrote:
> Cf. the way this is handled in Rust, where there is a clear distinction between a container, versus an iterator over that container:

That is true for C++ and Python as well. C++ has begin(object)/end(object) and Python has iter(object).

May 10, 2021
On Monday, 10 May 2021 at 12:19:07 UTC, deadalnix wrote:
> On Monday, 10 May 2021 at 04:21:34 UTC, Andrei Alexandrescu wrote:
>> So you have a range r of type T.
>>
>> You call r.popFront().
>>
>> Obvioulsly the type of r should stay the same because in D variables don't change type.
>>
>> So... what gives, young Padawan?
>>
>> No, this is not subtyping 101.
>
> If you have a range of T, then you got to return a T. I'm not sure what's the problem is here. Do you have a concrete example?
>
> All I can think of are things like slicing and alike, and they should obviously return a string, not a T.

More to the point, consider this:

class String {
private:
    immutable(char)[] value;

public:
    this(immutable(char)[] value) { this.value = value; }

    // ...
}

class EnumString : String {
public:
    static EnumString value1() { return new EnumString("value1"); }
    static EnumString value2() { return new EnumString("value2"); }

private:
    this(immutable(char)[] value) { super(value); }
}

While the implementation differs, conceptually, from a the theory standpoint, this is the same. This is using a subtype to constrain instance of type (String here) to a certain et of possible values. When using the subtype (EnumString) you have the knowledge that it is limited to some value, and you lose that knowledge as soon as you convert to the parent type.

But instead, we gets some bastardised monster from the compiler, that's not quit a subtype, but that's not quite something else that really make sens either. As expected, this nonsense ends up spilling into user code, and then the standard lib, based on user constraints, and everybody is left choosing between bad tradeof down the road because the whole house of cards is built on shaky foundations.

The bad news is, there is already a language like this. It's called C++, and it's actually quite successful. With all due respect to you and Walter, you guys are legends, but I think there is also a bit of learned helplessness coming from both of you due to a lifetime of exposure to the soul corroding effects of C++.

This attitudes pervades everything, and most language constructs suffer of some form of it in one way or another, causing a cascade of bad side effects, starting with this whole thread. A few examples en vrac for instance: DIP1000, delegate context qualifiers, functions vs first class functions, etc...

Back to the case of enum, it is obviously and trivially a subtype. In fact, even the syntax is the same:

enum Foo: string { ... }

Handling enum strings should never have been a special that was added to phobos, because it should never have been a special to begin with, in phobos or elsewhere.
May 10, 2021
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? Nowhere, the LSP absolutely doesn't mandate that. It mandate that you can pass a B to something that expects an A, and that thing will behave the way you'd expect.

And it does!

If your code needs an A, then you mark it as accepting an A as input. If I have a B and want to pass it to your code, I can too, transparently. You do not need to even know about the existence of B when your wrote your code. This is what the LSP is at its core.

Back to our string example, the code should accept string (A), with zero knowledge of the existence of any enum string (B). You should be able to pass a B to that code and have everything work as expected.

The argument that the enum string is not a subtype because it breaks he LSP is nonsense, this in fact demonstrate that the type system is unsound and it breaks LSP is broken. And this is why people end up desperately trying to re-implement it in libraries, which result in a ton of more work and complexity for everybody involved.
May 10, 2021
On Monday, 10 May 2021 at 21:44:02 UTC, deadalnix wrote:
> The bad news is, there is already a language like this. It's called C++, and it's actually quite successful. With all due respect to you and Walter, you guys are legends, but I think there is also a bit of learned helplessness coming from both of you due to a lifetime of exposure to the soul corroding effects of C++.

Not sure how this applies to C++, what subtyping issues are you having with C++?


> This attitudes pervades everything, and most language constructs suffer of some form of it in one way or another, causing a cascade of bad side effects, starting with this whole thread. A few examples en vrac for instance: DIP1000, delegate context qualifiers, functions vs first class functions, etc...

That's a direct result of the process. Features have always been added as an experiment rather than being completed on paper, even the ones with a DIP. At this point, this pretty much defines what D is... Just look at the addition of a C compiler that is being advanced right now. It is being added because there might be some benefits from it the future, perhaps. Of course, you also have the side effect that the AST becomes more resistant to change... and refactoring costs doubles...

So that is why D has these issues. People wanted something, and it was added in an experimental way, not in an analytical way. That is the way of D. Experiment in features.

Ideally D should have boosted meta programming and cut down on features to the bare minimum. Literals should have been a compile time type... and alias should bind to them, strings should've been a library construct, etc etc.

But if you look at the features being added, meta programming is not in focus. So this won't change. Features are being added that has nothing to do with metaprogramming (memory safety, C interop etc).

D will continue to evolve experimentally.

So there will never be a small core language that is consistent.

It is what it is, at this point.

May 10, 2021
On Monday, 10 May 2021 at 22:37:51 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 10 May 2021 at 21:44:02 UTC, deadalnix wrote:
>> The bad news is, there is already a language like this. It's called C++, and it's actually quite successful. With all due respect to you and Walter, you guys are legends, but I think there is also a bit of learned helplessness coming from both of you due to a lifetime of exposure to the soul corroding effects of C++.
>
> Not sure how this applies to C++, what subtyping issues are you having with C++?
>

Function type don't have the right covariance/contravariance, you can slice subtypes, and there are more, but this is not my point.

My point is that we already have a language that is a mixed bag of accidentally defined features that don't compose properly with each others. I don't need one more of these, I already have one, and, let's be frank, it has at the very least an order of magnitude more support in the wild, in tools and so on.

Doing the same thing with less manpower is a futile exercise.

>> This attitudes pervades everything, and most language constructs suffer of some form of it in one way or another, causing a cascade of bad side effects, starting with this whole thread. A few examples en vrac for instance: DIP1000, delegate context qualifiers, functions vs first class functions, etc...
>
> That's a direct result of the process. Features have always been added as an experiment rather than being completed on paper, even the ones with a DIP. At this point, this pretty much defines what D is... Just look at the addition of a C compiler that is being advanced right now. It is being added because there might be some benefits from it the future, perhaps. Of course, you also have the side effect that the AST becomes more resistant to change... and refactoring costs doubles...
>
> So that is why D has these issues. People wanted something, and it was added in an experimental way, not in an analytical way. That is the way of D. Experiment in features.
>

Sure, but look at this thread. D is crumbling under the weight, not of the number f feature, but of the fact that a large portion of them simply are unsound.

At this point, the decision made is to push the madness on the user. Fair enough, but if the standard lib devs are not willing to put up with it, why in hell would you expect anyone else to? Just look at what's in the C++ standard lib or boost and compare to your average C++ project to see the kind of gap in term of motivation to put up with bullshit exists between standard lib devs and Joe coder. It's not even close.

This stuff ain't working properly so let's just given getting to work at all is not how you iterate toward a great useful product.