Proposal: Object/?? Destruction

Timon Gehr timon.gehr at gmx.ch
Fri Oct 6 19:31:11 UTC 2017


On 06.10.2017 14:26, Steven Schveighoffer wrote:
> On 10/5/17 3:42 PM, Timon Gehr wrote:
>> On 05.10.2017 17:40, Steven Schveighoffer wrote:
>>> On 10/5/17 2:42 AM, Timon Gehr wrote:
>>>
>>>> The only unresolved question is (as using the result of the comma 
>>>> operator has been deprecated already): How to write a unary tuple. 
>>>> My favourite is what python does: "(3,)". This is however already 
>>>> accepted as a function argument list. I think it is worth breaking 
>>>> though. Maybe we should deprecate it.
>>>
>>> I know you have an answer for this, but pardon my ignorance.
>>
>> I indeed have strong opinions on how to do this correctly, as I have 
>> given some thought to it when designing the (still quite basic) type 
>> system of PSI: https://github.com/eth-srl/psi
>>
>> The idea is to follow type theory/mathematics where the type of 
>> functions is a binary type constructor taking domain and codomain to 
>> the type of functions mapping values from the domain to values from 
>> the codomain. Multiple function arguments are just the function 
>> applied to a tuple of values.
>>
>>> Why isn't (a) good enough?
>>>
>>
>> typeof((a)) should be typeof(a). This is just a parenthesized 
>> expression, as in (a+b)*c.
> 
> Right, I agree.
> 
>> typeof((a,)) should be (typeof(a),).
> 
> I guess my question is more in the context of the problem at hand:
> 
> int foo();
> 
> auto (a) = foo();
> 
> why can't this work?
> ...

This could be made to compile, but this is not really about tuples.

> But then of course, it shouldn't work, because int is not a tuple. So I 
> suppose I have answered my own question -- we need a way to specify a 
> tuple of one for prototype foo!
> 
> Indeed, my experience with tuples and their usage is quite limited.
> 
> Even though the syntax is straightforward and unambiguous, it looks 
> incorrect, like you forgot something.
> ...

That's not necessarily bad. (When is the last time you have used a 
singleton tuple?)

> I'm not an expert in language design, but would it be worth exploring 
> other punctuation that isn't used in the language currently to allow 
> better syntax? It seems like the trailing comma is to get around 
> ambiguity,

It's the comma that indicates tupling, so there is not really ambiguity, 
the expression (a) just is not a tuple. To indicate a tuple you need to 
use the tupling operator ','. Trailing commas are allowed for all 
tuples, but for singleton tuples they are also necessary.

> but there would be no ambiguity if you used something other 
> than current punctuation to surround the tuple.
> 
> Angle brackets come to mind <a>.

D avoids angle brackets.

> Also you could use a leading symbol to 
> change the meaning of the parentheses, like $(a).
> ...

This is very noisy, and once you go with non-standard tuple syntax, you 
can just as well use tuple(a).

>> ---
>> (int,) foo(int a){ return (a,); } // turn value into singleton tuple
>> int bar(int a,){ return a[0]; }   // turn singleton tuple into value
>>
>> void main(){
>>      foo(2,); // error: cannot convert (int,) to int
>>      bar(2); // error: cannot convert int to (int,)
>>      auto (x,) = foo(2); // ok, x has type int
>>      auto y = bar(2,); // ok y has type int
>>      auto z = foo(2); // ok, z has type (int,)
>> }
>> ---
>>
>> ---
>> // The following two function signatures are equivalent (identical 
>> name mangling):
>> (int,string) foo(int a,string b){
>>      return (a,b);
>> }
>>
>> (int,string) foo((int,string) x){
>>      return x;
>> }
>> ---
> 
> So I will ask, what is the usage of foo here?
> 
> In the first example (foo and bar), you can't call a function that takes 
> a tuple with a single value, and you can't call a function that takes a 
> value with a single tuple (BTW, this is not how AliasSeq works, you can 
> call functions that take a single arg with single element tuples).
> ...

AliasSeq auto-expands. If you call a function with a single element 
AliasSeq, it will expand to a single value and not be an AliasSeq 
anymore. Built-in tuples should not auto-expand, so a singleton tuple 
stays a singleton tuple (they will have an explicit .expand property).

> In your second example, where foo takes a 2-element tuple or 2 values, 

All functions take a single value. That value might be a tuple. (Of 
course, we will continue to say that a function can take multiple 
arguments, because it is convenient, but what this _means_ is that it 
takes a single tuple argument.)

> you say the name mangling is equivalent. Does that mean if I only define 
> the tuple version, I can call it with foo(1, "hello") and vice versa? 

Yes. (Both options are "the tuple version".)

> This seems to contradict your example above.
> ...

No. All functions take one argument and produce one result. (The 
argument and the result may or may not be a tuple, but there is no 
essential difference between the two cases.) You can match a value 
against a pattern on the function call. The following are equivalent:

(int,string) foo(){
     // unpack at initialization of local variables of `foo`
     // pattern: (int a, string b)
     // value:   (1,"2")
     (int a, string b) = (1,"2");
     return (a,b);
}

(int,string) foo(){
     auto match(int a, string b){
         return (a,b);
     }
     // unpack at initialization of parameters of 'match'
     // pattern: (int a, string b)
     // value:   (1,"2")
     return match(1,"2");
}

Consider the following two different ways to achieve the same result:

(int,string) foo(){
     (int a, string b) = (1,"2");
     return (a,b);
}

(int,string) bar(){
     (int,string) x = (1,"2");
     return x;
}

We can also rewrite bar in terms of a local match function:

(int,string) bar(){
     auto match((int,string) x){
         return x;
     }
     return match(1,"2");
}

Generally, if you have a function call like:

foo(...)

You can consider the part (...) in isolation as an expression. This will 
be your function argument:

foo();   // function argument: ()
foo(1);  // function argument: (1)
foo(2,); // function argument: (2,)
foo(1,2);// function argument: (1,2)

>> ---
>> auto id(T)(T x){ return x; }
>>
>> void main(){
>>      auto a = id(2); // ok, a is 2.
>>      auto b = id(1,2); // ok, b is (1,2)
>>      auto c = id(1,); // ok, c is (1,)
>> }
>> ---
>>
> 
> This would mess up a TON of code. I can say for certain, a single type 
> argument can never be made to accept a tuple.
> 
The proposal is to make all arguments "single type arguments". The 
"single type" might be a tuple. A tuple type is just a type, after all. 
For two current functions where only one matches but after the change 
both would match, the same one would still be selected, because it is 
more specialized.

I.e., if for some reason you have:

void foo(T)(T x){
     // ...
}
void foo(T,S)(T a,S b){
     // ...
}
void foo(T...)(T args){
     // ...
}

Then the call foo(2) will still go to the first overload and the call 
foo(1,2) will still go to the second overload, while the call foo(1,2,3) 
will still go to the third overload.


What relevant use case would break? (I can see the case where a 
cross-module overload becomes ambiguous, but that seems a little contrived.)


More information about the Digitalmars-d mailing list