October 06, 2017
On Friday, 6 October 2017 at 19:51:39 UTC, Timon Gehr wrote:
> No, under my thinking the original example should have been what it was.
> Enclosing an expression in an additional set of parentheses does not change its semantics. This is true even if one set of parentheses is part of the function call.

Hmm, I hadn't realized that you can have multiple sets of parentheses without error. I was assuming you would treat the second set as a tuple.
October 06, 2017
On 10/6/17 3:31 PM, Timon Gehr wrote:
> 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.

It is weird to me that a function with 2 parameters is the same as a function that takes a 2-element tuple, but a function with one parameter is not the same as a function that takes a 1-element tuple. That is where I feel it's a contradiction.

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

Right, but cases where T is expected to match to exactly one type will now match with multiple types. It messes up is(typeof(...)) checks.

-Steve
October 08, 2017
On 06.10.2017 23:34, Steven Schveighoffer wrote:
>>>
>>
>> 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.
> 
> It is weird to me that a function with 2 parameters is the same as a function that takes a 2-element tuple, but a function with one parameter is not the same as a function that takes a 1-element tuple. That is where I feel it's a contradiction.
> ...
If a function with 2 parameters was the same as a function that takes a 2-element tuple, and a function with one parameter that is a 2-element tuple is the same as a function that takes a 1-element tuple, then a function that takes a 2-element tuple is the same as a function that takes a 1-element tuple. So I think the opposite is the case.

// those two are the same
void foo(int a,string b); // match two-element tuple
void foo((int,string) x); // take two-element tuple w/o matching

// those two are the same
void bar(int a,);   // match one-element tuple
void bar((int,) x); // take one-element tuple w/o matching

This is like:

(int a,string b)=(1,"2"); // match
// vs
(int,string) x=(1,"2"); // w/o matching

and

(int a,)=(1,); // match
// vs
(int,) x=(1,); // w/o matching

In case this is not convincing to you: Why does your reasoning apply to arguments but not return values? Why should arguments not behave the same as return values? If it does actually apply to return values: what special syntax would you propose for functions that "return multiple values"? Is it really reasonable to not use tuples for that?

>>> 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.
> 
> Right, but cases where T is expected to match to exactly one type will now match with multiple types. It messes up is(typeof(...)) checks.
> 
> -Steve

All new language features can be detected using is(typeof(...)) this is usually ignored for language evolution. We'd need to check how much code relies on this specific case not compiling.

We can also think about adding a "light" version of tuple support, that just supports unpacking for library-defined tuple types and nothing else, but I'd prefer to have proper tuples.
October 08, 2017
On 10/7/17 8:56 PM, Timon Gehr wrote:
> On 06.10.2017 23:34, Steven Schveighoffer wrote:
>>>>
>>>
>>> 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.
>>
>> It is weird to me that a function with 2 parameters is the same as a function that takes a 2-element tuple, but a function with one parameter is not the same as a function that takes a 1-element tuple. That is where I feel it's a contradiction.
>> ...
> If a function with 2 parameters was the same as a function that takes a 2-element tuple, and a function with one parameter that is a 2-element tuple is the same as a function that takes a 1-element tuple, then a function that takes a 2-element tuple is the same as a function that takes a 1-element tuple. So I think the opposite is the case.
> 
> // those two are the same
> void foo(int a,string b); // match two-element tuple
> void foo((int,string) x); // take two-element tuple w/o matching
> 
> // those two are the same
> void bar(int a,);   // match one-element tuple
> void bar((int,) x); // take one-element tuple w/o matching
> 
> This is like:
> 
> (int a,string b)=(1,"2"); // match
> // vs
> (int,string) x=(1,"2"); // w/o matching
> 
> and
> 
> (int a,)=(1,); // match
> // vs
> (int,) x=(1,); // w/o matching

My questioning comes with this:

void bar(int a);
void bar((int,) x);

To me, it is confusing or at least puzzling that these two aren't the same.

The first is like a "regular" function that doesn't take a tuple.

The second is a new "tuplized" function that takes a tuple. both take one parameter (one version via the regular argument syntax, one via a tuplized syntax). Why is it not the same? Clearly a tuple of 1 can bind to a single value, just like a tuple of 2 can bind to 2 values.

Currently, I can call this:

foo(T...)(T t) if (T.length == 1) // function that takes a single element tuple

like this:

foo(1);

Why is this disallowed in your tuple scheme?

> In case this is not convincing to you: Why does your reasoning apply to arguments but not return values? Why should arguments not behave the same as return values? If it does actually apply to return values: what special syntax would you propose for functions that "return multiple values"? Is it really reasonable to not use tuples for that?

I don't understand the question. I would think single value tuples and single values would be pretty much interchangeable. It's up to the user of the value whether he wants to look at it as a tuple (which has length and must be indexed) vs. a single value.

>> Right, but cases where T is expected to match to exactly one type will now match with multiple types. It messes up is(typeof(...)) checks.
>>
> 
> All new language features can be detected using is(typeof(...)) this is usually ignored for language evolution. We'd need to check how much code relies on this specific case not compiling.

I definitely don't have an answer off hand, but I wouldn't be surprised if this broke at least some code in phobos.

> We can also think about adding a "light" version of tuple support, that just supports unpacking for library-defined tuple types and nothing else, but I'd prefer to have proper tuples.

This flew over my head :)

-Steve
October 09, 2017
On 09.10.2017 01:20, Steven Schveighoffer wrote:
> On 10/7/17 8:56 PM, Timon Gehr wrote:
>> On 06.10.2017 23:34, Steven Schveighoffer wrote:
>>>>>
>>>>
>>>> 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.
>>>
>>> It is weird to me that a function with 2 parameters is the same as a function that takes a 2-element tuple, but a function with one parameter is not the same as a function that takes a 1-element tuple. That is where I feel it's a contradiction.
>>> ...
>> If a function with 2 parameters was the same as a function that takes a 2-element tuple, and a function with one parameter that is a 2-element tuple is the same as a function that takes a 1-element tuple, then a function that takes a 2-element tuple is the same as a function that takes a 1-element tuple. So I think the opposite is the case.
>>
>> // those two are the same
>> void foo(int a,string b); // match two-element tuple
>> void foo((int,string) x); // take two-element tuple w/o matching
>>
>> // those two are the same
>> void bar(int a,);   // match one-element tuple
>> void bar((int,) x); // take one-element tuple w/o matching
>>
>> This is like:
>>
>> (int a,string b)=(1,"2"); // match
>> // vs
>> (int,string) x=(1,"2"); // w/o matching
>>
>> and
>>
>> (int a,)=(1,); // match
>> // vs
>> (int,) x=(1,); // w/o matching
> 
> My questioning comes with this:
> 
> void bar(int a);
> void bar((int,) x);
> 
> To me, it is confusing or at least puzzling that these two aren't the same.
> ...

Well, to me it is a bit confusing that this is puzzling to you. Why should int be the same as (int,)? It does not make sense to index an integer, but (int,) can be indexed with 0 to get an integer.

I believe your difficulty is rather with the notion that what before was a function that takes a single value is no longer analogous to what before was a function that takes multiple values. The analogy breaks because now they are handled precisely the same way, rather than just analogously. Furthermore, some existing syntax slightly changes meaning: The prior syntax for declaring multiple arguments is now a pattern that matches against a single tuple argument.

The new design is more orthogonal and in effect more useful, because functions no longer need to care about and interfere with the concept of "multiple values".

> The first is like a "regular" function that doesn't take a tuple.
> 
> The second is a new "tuplized" function that takes a tuple. both take one parameter (one version via the regular argument syntax, one via a tuplized syntax).

One takes an argument that is an integer. The other takes an argument that is a tuple containing a single integer.

> Why is it not the same?

One takes an int while the other takes an (int,).

> Clearly a tuple of 1 can bind to a single value, just like a tuple of 2 can bind to 2 values.
> ...

But it is precisely what is happening. However, not every value is a singleton tuple just by virtue of not being a tuple of multiple values.

> Currently, I can call this:
> 
> foo(T...)(T t) if (T.length == 1) // function that takes a single element tuple
> 
> like this:
> 
> foo(1);
> 
> Why is this disallowed in your tuple scheme?
> ...

I take this to mean, why does the following code not compile:

void foo(T)(T t) if(T.length == 1) { ... }

foo(1);

This is because T is matched to int, which does not have length.

A few more examples:

void foo(int x);       // foo takes an int
void foo(int x,);      // foo takes an (int,) and matches it against (int x,) in order to extract the value x
void foo(int x,int y); // foo takes an (int,int) and matches it against (int x,int y) in order to extract the values x and y.

>> In case this is not convincing to you: Why does your reasoning apply to arguments but not return values? Why should arguments not behave the same as return values? If it does actually apply to return values: what special syntax would you propose for functions that "return multiple values"? Is it really reasonable to not use tuples for that?
> 
> I don't understand the question.

I'm was trying to figure out what is causing the confusion. I was trying to appeal to symmetry to get past what seemed to be your notion that there is a significant difference between multiple values and a single tuple: If you have a function that needs to return multiple values, you return a single tuple. If you have a function that needs to take multiple values, you take a single tuple. With tuples, it is sufficient to return a single value, and it is also sufficient to take a single value as the argument.

> I would think single value tuples and single values would be pretty much interchangeable.

Well, no. Otherwise 2[0] would be allowed and equal to 2. And then, what would [2][0] be? [2] or 2?

> It's up to the user of the value whether he wants to look at it as a tuple (which has length and must be indexed) vs. a single value.
> ...

A singleton tuple is like a box that contains a single value. You need to open the box to get at the value. Opening the box is achieved by matching the tuple against a tuple pattern. This is the same regardless of the length of the tuple.

Singleton tuples might seem pointless, and some languages do not support such single-element tuples, but as we want to support slicing, they should probably exist. (Also, we might want to create a tuple from an AliasSeq, which can be achieved by dropping it into a single-element tuple and letting it auto-expand.)

>>> Right, but cases where T is expected to match to exactly one type will now match with multiple types. It messes up is(typeof(...)) checks.
>>>
>>
>> All new language features can be detected using is(typeof(...)) this is usually ignored for language evolution. We'd need to check how much code relies on this specific case not compiling.
> 
> I definitely don't have an answer off hand, but I wouldn't be surprised if this broke at least some code in phobos.
> ...

I guess we'll need to try.

>> We can also think about adding a "light" version of tuple support, that just supports unpacking for library-defined tuple types and nothing else, but I'd prefer to have proper tuples.
> 
> This flew over my head :)
> ...

If we cannot have proper tuples, having some syntactic sugar for tuple unpacking during variable declaration may still be useful:

import std.typecons;

auto (x,y) = tuple(1,"2");
(int x,string y) = tuple(1,"2");

This is syntactically forward-compatible.
October 09, 2017
On Monday, 9 October 2017 at 15:22:35 UTC, Timon Gehr wrote:
>
> Singleton tuples might seem pointless, and some languages do not support such single-element tuples, but as we want to support slicing, they should probably exist. (Also, we might want to create a tuple from an AliasSeq, which can be achieved by dropping it into a single-element tuple and letting it auto-expand.)
>

Singleton tuples would be necessary for any kind of recursion on tuples.

The way mir.functional.RefTuple works is similar to your AliasSeq tuple. This is because it uses anonymous names for the fields. std.typecons.Tuple assumes field name/Type pairs.
October 10, 2017
On 10/9/17 11:22 AM, Timon Gehr wrote:
> On 09.10.2017 01:20, Steven Schveighoffer wrote:
>>
>> My questioning comes with this:
>>
>> void bar(int a);
>> void bar((int,) x);
>>
>> To me, it is confusing or at least puzzling that these two aren't the same.
>> ...
> 
> Well, to me it is a bit confusing that this is puzzling to you. Why should int be the same as (int,)? It does not make sense to index an integer, but (int,) can be indexed with 0 to get an integer.

I understand why (int,) is different from int. What I meant was, why can't I *call* a function that takes a single int tuple with a single int value?

It shouldn't matter to the caller whether you plan to fiddle with your parameter via tuple syntax or directly with a value.

Again, I go back to the 2-parameter version. I can call it with 2 values, or a tuple of 2 values. It makes no difference to the callee how I call it, as long as I put 2 values on the stack.

I don't see why it should be different for a single parameter function.

To put it another way, in your scheme, what is the benefit to overloading a single value function call with a function call that takes a single element tuple? When would this be useful?

>> Currently, I can call this:
>>
>> foo(T...)(T t) if (T.length == 1) // function that takes a single element tuple
>>
>> like this:
>>
>> foo(1);
>>
>> Why is this disallowed in your tuple scheme?
>> ...
> 
> I take this to mean, why does the following code not compile:
> 
> void foo(T)(T t) if(T.length == 1) { ... }
> 
> foo(1);

Nope, I meant my original. A "tuple" as D currently uses it, can have exactly one element, and I can call that function with exactly one value. I don't have to call it as:

AliasSeq!(int) v;
v[0] = 1;
foo(v);

Which is analogous to your requirements (obviously, D is missing the syntax for tuple literals, which is why it's complicated).

Note that if foo is:

foo(int x);

I can still call it with v. I don't see why we can't keep these kinds of allowances.

>> I would think single value tuples and single values would be pretty much interchangeable.
> 
> Well, no. Otherwise 2[0] would be allowed and equal to 2. And then, what would [2][0] be? [2] or 2?

Not interchangeable in terms of usage, but interchangeable in terms of overloading.

What I would have expected is for foo(int) and foo((int,)) to be equivalent mangling (like the bar(int, int) and bar((int, int)) are equivalent), and for the caller to be able to call those functions with either a single value or a singleton tuple.

Inside the function, of course, they are treated differently as the callee decides whether to unpack the tuple or not via the parameters.

>>> We can also think about adding a "light" version of tuple support, that just supports unpacking for library-defined tuple types and nothing else, but I'd prefer to have proper tuples.
>>
>> This flew over my head :)
>> ...
> 
> If we cannot have proper tuples, having some syntactic sugar for tuple unpacking during variable declaration may still be useful:
> 
> import std.typecons;
> 
> auto (x,y) = tuple(1,"2");
> (int x,string y) = tuple(1,"2");
> 
> This is syntactically forward-compatible.

OK, this makes sense, yes.

-Steve
October 12, 2017
On 10.10.2017 17:05, Steven Schveighoffer wrote:
> On 10/9/17 11:22 AM, Timon Gehr wrote:
>> On 09.10.2017 01:20, Steven Schveighoffer wrote:
>>>
>>> My questioning comes with this:
>>>
>>> void bar(int a);
>>> void bar((int,) x);
>>>
>>> To me, it is confusing or at least puzzling that these two aren't the same.
>>> ...
>>
>> Well, to me it is a bit confusing that this is puzzling to you. Why should int be the same as (int,)? It does not make sense to index an integer, but (int,) can be indexed with 0 to get an integer.
> 
> I understand why (int,) is different from int. What I meant was, why can't I *call* a function that takes a single int tuple with a single int value?
> ...

Because this would require a special-case rule that I had not considered so far. This is up to discussion though.

I interpreted your question to be: "Why do your proposed rules not lead to my expected behaviour?", and not: "Why do your rules not allow this?", but it seems I have misinterpreted your question. Sorry for the confusion! :)

> It shouldn't matter to the caller whether you plan to fiddle with your parameter via tuple syntax or directly with a value.
> ...

I see. I think what you propose does make sense, as it might smoothen out the interaction with other D language features such as variadics and overloading.

> Again, I go back to the 2-parameter version. I can call it with 2 values, or a tuple of 2 values.

With the caveat that those two cases are actually identical, yes.

auto x = (1,"2"); // construct value
f(x); // now call f with value

and

f(1,"2"); // construct tuple and call f with the resulting value

It is like:

auto x = [1,2];
f(x);

and

f([1,2]);

Except that redundant parentheses are optional:
f(1,"2") is exactly equivalent to f((1,"2")) after parsing.

The second expression just adds an additional pair of parentheses around f. f(((1,"2"))) is also the same expression for the same reason.

Note that this does not compile:

void f(int a,int b,int c){}
f(1,(2,3));

The reason is that I tried to call f with an argument of type (int,(int,int)), while it expected an argument of type (int,int,int).

> It makes no difference to the callee how I call it, as long as I put 2 values on the stack.
> ...

Well, I think it should maybe not be possible to conflate e.g. (int,int) and ((int,),(int,)).

> I don't see why it should be different for a single parameter function.
> ...

I think you are making a strong point here.

> To put it another way, in your scheme, what is the benefit to overloading a single value function call with a function call that takes a single element tuple? When would this be useful?
> ...

I agree that this is actually not useful.

Note that this means the following code will be accepted also:

void foo(int x,int y,int z){}

foo((1,2,3),);

Does this match your expectation?

>>> Currently, I can call this:
>>>
>>> foo(T...)(T t) if (T.length == 1) // function that takes a single element tuple
>>>
>>> like this:
>>>
>>> foo(1);
>>>
>>> Why is this disallowed in your tuple scheme?
>>> ...
>>
>> I take this to mean, why does the following code not compile:
>>
>> void foo(T)(T t) if(T.length == 1) { ... }
>>
>> foo(1);
> 
> Nope, I meant my original. A "tuple" as D currently uses it, can have exactly one element, and I can call that function with exactly one value. I don't have to call it as:
> 
> AliasSeq!(int) v;
> v[0] = 1;
> foo(v);
> 
> Which is analogous to your requirements (obviously, D is missing the syntax for tuple literals, which is why it's complicated).
> 
> Note that if foo is:
> 
> foo(int x);
> 
> I can still call it with v. I don't see why we can't keep these kinds of allowances.
> ...

I see. Well, we can't keep them to the extent AliasSeq has them. AliasSeq always auto-expands. Auto-expansion for tuples can become a problem, especially in generic code, because it forgets structure information. For example:

void printElements(T)(T[] arr){
    foreach(x;enumerate(a)){
        print("at index ",x[0]," we have ",x[1]);
    }
}

auto a = [(1,2),(3,4),(5,6)];
printElements(a);

With auto-expansion, this prints:
at index 0, we have 1
at index 1, we have 3
at index 2, we have 5

However, it is quite clear from the definition of printElements that the programmer wanted it to print:

at index 0, we have (1,2)
at index 1, we have (3,4)
at index 2, we have (5,6)

AliasSeq does not have this specific problem, because it cannot be put into an array without expanding.

>>> I would think single value tuples and single values would be pretty much interchangeable.
>>
>> Well, no. Otherwise 2[0] would be allowed and equal to 2. And then, what would [2][0] be? [2] or 2?
> 
> Not interchangeable in terms of usage, but interchangeable in terms of overloading.
> 
> What I would have expected is for foo(int) and foo((int,)) to be equivalent mangling (like the bar(int, int) and bar((int, int)) are equivalent), and for the caller to be able to call those functions with either a single value or a singleton tuple.
> 
> Inside the function, of course, they are treated differently as the callee decides whether to unpack the tuple or not via the parameters.
> 

This makes sense, and I think your proposal improves the design.
October 14, 2017
I've thought about tuples and stuff for a while. For tuples, I'll use [brackets]. Reasons follow.

Homogeneous tuples are repetitions of some single type. We have them today in form of static arrays. We could allow "inhomogeneous arrays" and call them tuples. T[n] is then an alias for [T, T, .., T] with n repititions. In place of a type [T, S] means Tuple!(T, S) and in place of an Object [t, s] means tuple(t, s). Note that D's grammar allows disambiguate types and objects by syntax.

A tuple implicitly converts to some other, if the pointwise types do. Bracket literals constitute a separate type that exists in the compiler only. We have that already to make
   int[2] a = [ 1, 2 ];
not allocate on the heap, but
   int[]  a = [ 1, 2 ];
does. So at first, [ 1, 2.0 ] is of type [int, double]. If you assign it to a double[2], because int -> double, the conversion is no problem. The thing that changes, is when you ask for typeof([ 1, 2.0 ]) directly. Of course,
   auto tup = [ 1, 2.0 ];
will homogenize the tuple to double[] similar to how it does today. Declaration-decomposition can be done as
   auto [a, b] = f(x);
(non-exlusive) or
   [auto a, auto b] = f(x);
The first one is shorter, the latter one let's you do
   [int a, auto b] = f(x);
So auto [x1, .. xn] is just a shorthand for [auto x1, .. auto xn].
Assignment-decomposition is the same with the types/auto missing.
Swap can be done with
   [a, b] = [b, a];
From the type system, if a tuple literal has only lvalues inside, it is an lvalue, too.
Note that there must be some way to handle side effects correctly. The problem is already known from normal assignment.

1-tuples are in a natural way included. int[1] is today different from int. When we have first-class tuples in D, we should not distinguish static arrays from homogeneous tuples.

Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]). I find the syntax (1,) for 1-tuples weird. Parenthesis are used only for operator precedence and function calls. They should not be used for tuples -- the 1-tuple case and f(1, 2) vs f((1, 2)) prove that. Parenthesis are a tool of syntax, not semantics. You can never omit brackets in D.

Maybe you can use some of that as input for your DIP.
October 14, 2017
On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:
> Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]).

But in f([1, 2]), it's ambiguous (just by parsing) whether [1, 2] is a tuple literal or a dynamic array literal.

You'd need to use a prefix or something to the bracket syntax.  E.g., $[1,2].  The dollar sign is just an example, but it might work because currently $ only seems to be used in slice syntax and in inline assembly.  I don't think tuple literals are needed in inline assembly, and I think the slice syntax might be okay because (unlike C) D doesn't allow the borked 0[arr] style of indexing, so expressions like arr[0..$[x][y]] are unambiguous.  (I.e., $[x][y] is the yth element of a tuple containing x, and not the yth element of the $th element of x as an array).