July 05, 2022

On 7/5/22 2:34 AM, bauss wrote:

>

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 #.

I thought that too. But there is existing syntax that might interfere: cond ? value1 :value2.

I don't know enough to be sure, so I just picked one that isn't used.

-Steve

July 06, 2022
On 7/1/22 21:20, Steven Schveighoffer wrote:
> On 7/1/22 2:22 PM, Walter Bright wrote:
>> On 7/1/2022 8:42 AM, Steven Schveighoffer wrote:
>>> Yes, but this is not exactly a case without precedent e.g.:
>>>
>>> ```d
>>> short[] x = [1, 2, 3]; // short[]
>>> auto y = [1, 2, 3]; // int[]
>>>
>>> void foo(short[] arr);
>>> foo([1, 2, 3]); // works
>>> ```
>>
>> That's because the initializer, *after* it is evaluated for its type, gets cast to the type of the lvalue.
> 
> Not really.
> 
> ```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.
> 
> What I am talking about is a thing that has not yet been given a type, and becomes the type you need if it's a valid use. Just like an array literal -- it's a complex expression that maintains it's polysemous quality. The only difference is, an enum `#value` would not have a default type.
> 
> -Steve

An even better analogy:

```d
void main(){
    auto dg = x=>x; // error
    // ...
}
```

This lambda does not have a default type, but it will infer the parameter type if one is provided:

```d
void main(){
    int delegate(int) dg = x=>x; // ok
    // ...
}
```

It can even match two distinct types:

```d
void foo(int delegate(int) dg){}
void foo(float delegate(float) dg){}

void main(){
    foo(x=>x); // error, ambiguous
    // ...
}
```

I.e., in terms of type checking, everything has already been figured out and works:

```d
import std;

alias Poly(T)=T delegate(T=T.init);
enum poly(string name)=q{
    (x){
        static if(is(typeof(typeof(x).%s)))
            return typeof(x).%s;
    }
}.format(name,name);

enum Foo{
    a,
    b,
}
enum Bar{
    b,
    c,
}

void foo(Poly!Foo foo){ writeln(foo()); }
void foo(Poly!Bar bar){ writeln(bar()); }

void main(){
    foo(mixin(poly!"a")); // ok, calls first overload
    // foo(mixin(poly!"b")); // error, matches both
    foo(mixin(poly!"c")); // ok, calls third overload
}
```

So it's just a matter of reusing this logic. How reusable it is I don't know, but I guess it has been debugged before.

Therefore, I think the main question that remains is syntax, semantics is a solved problem. (Or more pessimistically, any bugs in semantics are already in the compiler today.)
July 06, 2022
On 7/1/22 21:37, Steven Schveighoffer wrote:
> On 7/1/22 2:32 PM, Walter Bright wrote:
>> On 7/1/2022 8:42 AM, Steven Schveighoffer wrote:
>>> How I would design it:
>>
>> Those are reasonable suggestions.
>>
>> But just let me throw this out. I've heard from many sources that nobody understands C++ function overloading. It is described with pages of detail in the Standard. Even compiler writers only *temporarily* understand it while they are implementing it.
>>
>> How C++ programmers deal with it is they randomly try things until it works.
> 
> This is why I'm asking the questions. I don't have experience on how to implement function overloading, and I don't know what pitfalls might be introduced from doing something like this.

I have implemented D's pretty simple function overloading rules in my experimental frontend. If you get a name clash you might get an ambiguity error, and other details are a consequence of the existing overloading rules, e.g.:

```d
import std;

alias Poly(T)=T delegate(T=T.init);
enum poly(string name)=q{
    (x){
        static if(is(typeof(typeof(x).%s)))
            return typeof(x).%s;
    }
}.format(name,name);

enum Foo{
    a,
    b,
}
enum Bar{
    b,
    c,
}

void fun(Poly!Foo foo, int x){ writeln(foo()); }
void fun(Poly!Bar bar, float x){ writeln(bar()); }

void main(){
    fun(mixin(poly!"b"), 1); // what does this do?
}
```

(Answer: It will call the first overload, because the lambda is considered a perfect match after type deduction.)

So I guess actually there is one open question: At what matching level should a polysemous enum literal match an enum type? If it's considered a perfect match, other parameters might cause a resolution of a name clash, if it's considered a match with implicit conversion, this does not happen.

Some may therefore prefer to consider it a match with implicit conversion so the compiler is more conservative and happier to deal out ambiguity errors. As different enum types are incompatible, you will never get a resolution because an overload is more specialized unless all matching overloads are with the same enum type.

In any case, everything is easily explained with the existing overloading rules plus one new definition of a matching level for polysemous enum literals. The pitfalls remain basically the same.
July 05, 2022

On 7/5/22 7:53 PM, Timon Gehr wrote:

>

So I guess actually there is one open question: At what matching level should a polysemous enum literal match an enum type? If it's considered a perfect match, other parameters might cause a resolution of a name clash, if it's considered a match with implicit conversion, this does not happen.

I'm concerned about that though, because this brings what should be an exact match (if you specified the enum fully) into the same category as an implicit conversion.

For example:

enum E { one }

foo(E e, uint u); // 1
foo(E e, int i); // 2

foo(E.one, 1U); // calls 1
foo(E.one, 1); // calls 2

// Now both foo's are at the same level, and there's an ambiguity error
// because uint <-> int
foo(#one, 1U);

I'd much rather just consider that the type is deferred until the expression is used, and then resolved to the correct type based on the way it's used. The sole focus of this feature should be to just omit enum names when they are obvious.

-Steve

July 06, 2022
On 7/6/22 02:57, Steven Schveighoffer wrote:
> On 7/5/22 7:53 PM, Timon Gehr wrote:
>> So I guess actually there is one open question: At what matching level should a polysemous enum literal match an enum type? If it's considered a perfect match, other parameters might cause a resolution of a name clash, if it's considered a match with implicit conversion, this does not happen.
> 
> I'm concerned about that though, because this brings what should be an exact match (if you specified the enum fully) into the same category as an implicit conversion.
> 
> For example:
> 
> ```d
> enum E { one }
> 
> foo(E e, uint u); // 1
> foo(E e, int i); // 2
> 
> foo(E.one, 1U); // calls 1
> foo(E.one, 1); // calls 2
> 
> // Now both foo's are at the same level, and there's an ambiguity error
> // because uint <-> int
> foo(#one, 1U);
> ```
> 
> I'd much rather just consider that the type is deferred until the expression is used, and then resolved to the correct type based on the way it's used. The sole focus of this feature should be to just omit enum names when they are *obvious*.
> 
> -Steve

Then you get the same behavior as with lambdas.

```d
enum Foo{ a, b }
enum Bar{ b, c }

void fun(Foo foo, int x){ writeln(foo); }
void fun(Bar bar, float x){ writeln(bar); }

void main(){
    fun(:b, 1);
}
```

So I guess you are saying this should call the first overload?

The reason I am bringing this up is that here, those calls are both legal:

```d
fun(Foo.b, 1); // calls first overload
fun(Bar.b, 1); // calls second overload
```

I don't think this is a huge problem, but it might qualify as a pitfall. (It could be argued that removing the enum name from working code should either preserve the behavior or error out.) The overloading rules are not getting any more complicated though.
July 05, 2022

On 7/5/22 9:12 PM, Timon Gehr wrote:

>

Then you get the same behavior as with lambdas.

enum Foo{ a, b }
enum Bar{ b, c }

void fun(Foo foo, int x){ writeln(foo); }
void fun(Bar bar, float x){ writeln(bar); }

void main(){
     fun(:b, 1);
}

So I guess you are saying this should call the first overload?

Yeah, I think this is better, it's what I would expect. But I can see your point. It's also fine your way as well. The fact that there's very few types that convert to each other limits this case to very few overload sets.

>

The reason I am bringing this up is that here, those calls are both legal:

fun(Foo.b, 1); // calls first overload
fun(Bar.b, 1); // calls second overload

I don't think this is a huge problem, but it might qualify as a pitfall. (It could be argued that removing the enum name from working code should either preserve the behavior or error out.) The overloading rules are not getting any more complicated though.

Either way is arguable I think. There is something to be said for erring on the side of caution (or ambiguity in this case).

-Steve

July 06, 2022

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

>

I thought that too. But there is existing syntax that might interfere: cond ? value1 :value2.

I don't know enough to be sure, so I just picked one that isn't used.

-Steve

I don't think that would interfere, because cond ? value1 value2 isn't allowed, so if value2 is an enum value then it would be cond ? value1 : :value2

July 06, 2022

On 7/6/22 3:05 AM, bauss wrote:

>

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

>

I thought that too. But there is existing syntax that might interfere: cond ? value1 :value2.

I don't know enough to be sure, so I just picked one that isn't used.

I don't think that would interfere, because cond ? value1 value2 isn't allowed, so if value2 is an enum value then it would be cond ? value1 : :value2

Probably fine, I agree. Again, just being cautious. I will stress also that my point isn't to propose a specific syntax, but a specific idea.

Note that even if there is not an ambiguity in the grammar, there is still the confusion of the person reading such code.

-Steve

July 06, 2022
On 7/5/2022 4:19 PM, Timon Gehr wrote:
> I.e., in terms of type checking, everything has already been figured out and works:

That example just hurts my brain :-/
July 07, 2022
On Wednesday, 6 July 2022 at 18:30:10 UTC, Walter Bright wrote:
> On 7/5/2022 4:19 PM, Timon Gehr wrote:
>> I.e., in terms of type checking, everything has already been figured out and works:
>
> That example just hurts my brain :-/

Side note: Does D really needs function overloading? You can achieve the same effect by using meta-programming.

- Alex