July 01, 2022
On 7/1/2022 12:20 PM, Steven Schveighoffer wrote:
> ```d
> auto a = [1, 2, 3];
> short[] b = [1, 2, 3];
> auto c = cast(short[])a;
> writeln(b); // [1, 2, 3]
> writeln(c); // [1, 0, 2, 0, 3, 0]
> ```
> 
> casting the literal is not the same as casting a regular value of that type.

It's casting the elements, not the array.
July 01, 2022
On 7/1/2022 12:37 PM, Steven Schveighoffer wrote:
> There's 2 questions to answer here:
> 
> 1) is it possible and feasible?

I don't know.

> 2) do we want to add to the complexity of the language?
> 
> Obviously if 1 is false, it's a non-starter. And it looks like you have indirectly answered that it is possible.
> 
> So on to 2:
> 
> IMO, I think the language benefits greatly from little syntax tricks like this. `foreach` is a great improvement over `for`. omitting parentheses can cause problems, but in general makes things far more pleasant to write/read.
> 
> I also have experience using a language (Swift) which has this feature, and it's really really nice, especially when dealing with verbose enums.

I find this puzzling, because in all my years I have *never* wanted non-scoped enums. C has them, I am not unfamiliar with them. I've also *never* heard this desire in 20 years of D.


> When using D enums after using Swift, I get the same feeling I do when I use C# or C++ after experiencing D metaprogramming. It feels like the D compiler knows all the information to make this simple, it just is asking me to jump through a few more hoops than it should.
> 
> I am genuinely curious why this will add too much to the complexity of overloading.
> 
> The current rules are:
> 
> 1. no match
> 2. match with implicit conversions
> 3. match with qualifier conversion (if the argument type is qualifier-convertible to the parameter type)
> 4. exact match
> 
> That shouldn't change.
> 
> The next part says:
> 
>     Each argument (including any this reference) is compared against the function's corresponding parameter to determine the match level for that argument. The match level for a function is the worst match level of each of its arguments.
> 
> OK, so a #value expression would be an "exact match" of any enum type that contains all the value literals in that expression, and a a "no match" for any other situation. The rest of the rules would apply as-is, including partial ordering, etc.
> 
> Since enums are not related, they should be completely distinct in terms of partial ordering.
> 
> Am I misunderstanding something?

For one thing, you'll have to add all the members of all the enums to the symbol lookup. What if they collide? Now you have an argument that may be a member of an arbitrary collection of different enums - which one do you pick for matching porpoises? If you say "try them all for a best match", now you've got a combinatorial explosion if there are several such parameters.

C solves this by not having overloading. But it still has a massive name collision problem, which is why C programmers routinely use the enum identifier as prefix for the member names - essentially a homemade way of scoping them.

C++ solved the problem by adding scoped enums.


>> BTW, if you really want a specific case to work:
>>
>>      enum A { a, b, c }
>>      alias a = A.a;
>>      alias b = A.b;
>>      alias c = A.c;
>>
>> This could probably be automated with metaprogramming.
> 
> This doesn't scale well.
> 
> ```d
> enum A { a, b, c }
> alias a = A.a;
> alias b = A.b;
> alias c = A.c;
> 
> void bar(A a) {}
> 
> void foo()
> {
>     int a = 5;
>     bar(a); // error;
> }

Doesn't your proposal have the same error?


> --- mod2.d:
> import mod1;
> int a = 5;
> void foo()
> {
>     bar(.a); // error;
> }
> ```
> 
> And I also have used `with` statements, which *mostly* works, but I've come across some head-scratching problems when naming conflicts occur.

Naming conflicts are worse with the proposed rules.

July 01, 2022

On 7/1/22 5:23 PM, Walter Bright wrote:

>

On 7/1/2022 12:37 PM, Steven Schveighoffer wrote:

>

I also have experience using a language (Swift) which has this feature, and it's really really nice, especially when dealing with verbose enums.

I find this puzzling, because in all my years I have never wanted non-scoped enums. C has them, I am not unfamiliar with them. I've also never heard this desire in 20 years of D.

These are not unscoped enums. There is a fundamental disconnect with what I wrote and what you are thinking I wrote. Swift's enums are not actually as loose type-wise as D's. They cannot convert to anything implicitly.

> >

Am I misunderstanding something?

For one thing, you'll have to add all the members of all the enums to the symbol lookup.

No, you don't. You only need to resolve the names when you are checking whether a function call matches, or an assignment/operation matches.

>

What if they collide? Now you have an argument that may be a member of an arbitrary collection of different enums - which one do you pick for matching porpoises? If you say "try them all for a best match", now you've got a combinatorial explosion if there are several such parameters.

It's a combinatorial explosion only if you wrote function overloads for all the possible combinations. Remember, this doesn't (can't) IFTI enum types from #values. It has to be concrete.

>

C solves this by not having overloading. But it still has a massive name collision problem, which is why C programmers routinely use the enum identifier as prefix for the member names - essentially a homemade way of scoping them.

C++ solved the problem by adding scoped enums.

C++ scoped enums are more like Swift in that they don't implicitly convert to the base type. That's not what I'm looking for. What I'm looking for is deferring the lookup of enum member names until we know the type.

> >

This doesn't scale well.

enum A { a, b, c }
alias a = A.a;
alias b = A.b;
alias c = A.c;

void bar(A a) {}

void foo()
{
    int a = 5;
    bar(a); // error;
}

Doesn't your proposal have the same error?

No

bar(#a); // cannot match int a
> >

--- mod2.d:
import mod1;
int a = 5;
void foo()
{
    bar(.a); // error;
}


And I also have used `with` statements, which *mostly* works, but I've come across some head-scratching problems when naming conflicts occur.

Naming conflicts are worse with the proposed rules.

No, they aren't. Because enum #values can only match enums.

I wrote this little piece of code to demonstrate a proof of concept feature (based on existing D syntax). It only works for equality, but probably could be extended for other operations. It will not work for the full features I am looking for (i.e. assignment and implicit conversion for function parameters).

But it gives you an idea how you can defer the type checking until it's needed.

struct EnumValComparer
{
    struct EVC(string s) {
        bool opEquals(T)(T val) if (is(T == enum))
        {
            static assert(is(typeof(__traits(getMember, T, s))));
            return __traits(getMember, T, s) == val;
        }
    }
    @property auto opDispatch(string s)() {
        return EVC!s();
    }
}

EnumValComparer ev;

enum ReallyReallyLongName
{
    one,
    two,
    three
}

void main()
{
    auto x = ReallyReallyLongName.one;
    assert(x == ev.one);
}

imagine instead of ev.one you wrote #one.

You can imagine that the compiler can figure this stuff out for everything, including arbitrary expressions between enum values, because it already is dealing with AST, and not simply strings.

-Steve

July 02, 2022

On Friday, 1 July 2022 at 10:49:46 UTC, user1234 wrote:

>

On Friday, 1 July 2022 at 09:02:12 UTC, Ola Fosheim Grøstad wrote:

>

C and D don't have an enumeration type. enum class gave C++ a somewhat type safe enumeration type. It is very useful.

Absolutely. This is why we can write this insanity:

enum E {absolutely}
const a = E.absolutely.absolutely.absolutely.absolutely.absolutely;

Isn't that just because a value also has the properties of its type? So even if D was strict about only using declared values for an enum type instance, your example would still work.

July 02, 2022

Just right now i got annoyed with enums again while porting an old library

screenshot

ctx.allocator.create!(JSONValue)(JSONValue.ValueType.NULL)

vs

ctx.allocator.create!(JSONValue)(.NULL)
ctx.allocator.create!(JSONValue)(#NULL)
ctx.allocator.create!(JSONValue)(_.NULL)

Unnecessary repetitions and verbosity in my opinion

I could put the with() next to the switch, but still, it's moving the problem

July 03, 2022

On Friday, 1 July 2022 at 15:42:20 UTC, Steven Schveighoffer wrote:

>

(Snip)

This is exactly how it should work. I think this is a pretty good feature. You want the strong typing, without namespace pollution, and without having to type everything out. It looks like the other C replacement languages see the value in it.

Ada of all languages actually works very similarly:

function foo is
  type thing is (a, b, c);
  type thang is (b, c, a);

  x : thing := a;
  y : thang := a; -- no ambiguity
begin
  -- ...
end foo;

We should learn a thing or two from Ada type features! Like using enums and ranges of enums as array bounds, for example. Ada is not a smooth language, but they somehow nailed stuff like this.

July 04, 2022
On 7/1/2022 3:48 PM, Steven Schveighoffer wrote:
> These are not unscoped enums. There is a fundamental disconnect with what I wrote and what you are thinking I wrote. Swift's enums are not actually as loose type-wise as D's. They cannot convert to anything implicitly.

I agree that disallowing implicit conversions is an important part of your proposal.

>>> Am I misunderstanding something?
>>
>> For one thing, you'll have to add all the members of all the enums to the symbol lookup.
> 
> No, you don't. You only need to resolve the names when you are checking whether a function call matches, or an assignment/operation matches.

Of course, that precludes `typeof(a)`, `a.sizeof`, `a + 3`, function templates, variadic function parameters.

How do you distinguish an instance of an elem with the same name as an element of the enum?


> It's a combinatorial explosion *only if* you wrote function overloads for all the possible combinations.

This implies deferring resolving expressions until trying to do matching, and constantly restarting that for each overload.


> C++ scoped enums are more like Swift in that they don't implicitly convert to the base type.

I didn't know that.


>>> enum A { a, b, c }
>>> alias a = A.a;
>>> alias b = A.b;
>>> alias c = A.c;
>>>
>>> void bar(A a) {}
>>>
>>> void foo()
>>> {
>>>     int a = 5;
>>>     bar(a); // error;
>>> }
>>
>> Doesn't your proposal have the same error?
> 
> No

I understand what you're doing, but confess that makes me uncomfortable.


> I wrote this little piece of code to demonstrate a proof of concept feature (based on existing D syntax). It only works for equality, but probably could be extended for other operations. It will not work for the full features I am looking for (i.e. assignment and implicit conversion for function parameters).
> 
> But it gives you an idea how you can defer the type checking until it's needed.

It's clever. With that, is a language change justified?
July 05, 2022

On Monday, 4 July 2022 at 22:34:01 UTC, Walter Bright wrote:

>

How do you distinguish an instance of an elem with the same name as an element of the enum?

By the # in front, right? That makes it a literal, not a plain identifier.

July 04, 2022

On 7/4/22 6:34 PM, Walter Bright wrote:

>

On 7/1/2022 3:48 PM, Steven Schveighoffer wrote:

>

No, you don't. You only need to resolve the names when you are checking whether a function call matches, or an assignment/operation matches.

Of course, that precludes typeof(a), a.sizeof, a + 3, function templates, variadic function parameters.

Yes. The point is to avoid having to write BigLongEnumTypeName, and that's it. If you use it in places where it's not obvious it should be BigLongEnumTypeName, it fails.

This does mean that in some cases, you just can't use this feature, and that would just have to be the way it is.

One drawback of swift pulling out all the stops to infer everything is sometimes it can't, and that leads to annoying messages like "took too long to figure out, please split up your expression". I don't want that outcome at all for D.

>

How do you distinguish an instance of an elem with the same name as an element of the enum?

The literal syntax distinguishes it from everything else. My strawman was #value, but really, any not-already-used syntax will do. .value is not available, neither is value. The prefix # means "this is an enum member". It can't be confused with anything else, just like you can't have symbols named 1.

I will stress that I'm not attached to # at all, it's just an easy-to-type symbol that doesn't already have an existing meaning. Another one that might work is $, because I think it would be unambiguous in this context, but also not sure.

> >

It's a combinatorial explosion only if you wrote function overloads for all the possible combinations.

This implies deferring resolving expressions until trying to do matching, and constantly restarting that for each overload.

I don't feel like this is that difficult, but I profess ignorance on how it currently works. I imagine you could have an EnumValueExpression AST node, which contains the list of members that must be valid. Then when trying to match, you check the list of members against the enum itself.

> > > >

enum A { a, b, c }
alias a = A.a;
alias b = A.b;
alias c = A.c;

void bar(A a) {}

void foo()
{
    int a = 5;
    bar(a); // error;
}

Doesn't your proposal have the same error?

No

I understand what you're doing, but confess that makes me uncomfortable.

Understandable. It's not something I really have enough knowledge to make you feel more comfortable with it. This is like a guy who "knows a little bit about engines" explaining to a mechanic what he wants done to his car. I hope someone with more compiler experience can jump in and give some more solid evidence that it's sound. Or alternatively explain why it's a really bad idea.

> >

I wrote this little piece of code to demonstrate a proof of concept feature (based on existing D syntax).

It's clever. With that, is a language change justified?

Yes, because I can't hook assignment, construction, function parameter conversion, etc. I.e. I can't hook implicit conversion to a target type. I can hook any kind of binary operation, because opBinaryRight exists, but that would leave something that is half clever but fails in a lot of cases. Enough to make it more frustrating than is worth it.

However, it does show the same methodology that I am thinking of -- defer the type resolution until it's used, just save the name(s).

-Steve

July 05, 2022

On Tuesday, 5 July 2022 at 01:15:28 UTC, Steven Schveighoffer wrote:

>

The literal syntax distinguishes it from everything else. My strawman was #value, but really, any not-already-used syntax will do. .value is not available, neither is value.

-Steve

Perhaps : could work as :value isn't currently a reserved syntax today, unless something comes before, so it should definitely work in this case.

It also looks less clunky than #.