October 05, 2017
On Thursday, 5 October 2017 at 06:42:14 UTC, Timon Gehr wrote:
>
> Why curly braces? Multiple function arguments are a form of built-in tuple, so the syntax should be consistent:
>
> auto (success, message) = callVoldemortFunction();
>
> 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.

The curly bracket syntax looks straight out of DIP 32

https://wiki.dlang.org/DIP32
auto {x, y} = {1, "hi"};.
October 05, 2017
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?
> 
> -Steve

typeof((a)) should be typeof(a). This is just a parenthesized expression, as in (a+b)*c.

typeof((a,)) should be (typeof(a),).

(I'm not super keen on conflating the type of a tuple with a tuple of types, but this has precedent in alias sequences, and I think it will be intuitive to most D users. FWIW, Haskell also does this.)

My intention is to disentangle the concepts "function argument" and "multiple values" as much as possible.

For example:

---
(int,string,double) foo(int a,string b,double c){
    return (a,b,c);
}

(int,string) bar(int a,string b,double c){
    return (a,b);
}

void main(){
    auto x = foo(1,"2",3.0); // ok, typeof(x) is (int,string double)
    //          ^^^^^^^^^^^
    // syntax of function argument is the same as the
    // syntax for a free-standing tuple:
    auto y = (1,"2",3.0);

    // all functions take a single argument, so you can construct
    // the tuple either at the call site, or before that:
    (int a,string b,double c) = foo(y); // ok
    auto (x,y,z) = foo(a,b,c); // ok

    // This allows natural composition of functions.
    // It is like DIP 35 except better:
    writeln([(1,"2",3.0), (4,"5",6.0)].map!foo.map!bar);
}
---

---
(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;
}
---

---
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,)
}
---

October 05, 2017
On 05.10.2017 17:23, Seb wrote:
>>>
>>> auto {success, message} = callVoldermortFunction();
>>>
>>>   This is concept is used in Kotlin. JavaScript es6 takes it even further (function parameters and arguments support object destruction)
>>>
>>>
>>
>> Why curly braces? Multiple function arguments are a form of built-in tuple, so the syntax should be consistent:
>>
>> auto (success, message) = callVoldemortFunction();
> 
> 
> I think I can state the opinion of many D users here: I don't mind whether it will be curly braces or round parentheses - the important thing is that we will be able to use it in the foreseeable future :)
> 
>> 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.
> 
> +1

I'll create a DIP as soon as I can.
October 05, 2017
On 05.10.2017 17:48, jmh530 wrote:
> On Thursday, 5 October 2017 at 06:42:14 UTC, Timon Gehr wrote:
>>
>> Why curly braces? Multiple function arguments are a form of built-in tuple, so the syntax should be consistent:
>>
>> auto (success, message) = callVoldemortFunction();
>>
>> 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.
> 
> The curly bracket syntax looks straight out of DIP 32
> 
> https://wiki.dlang.org/DIP32
> auto {x, y} = {1, "hi"};.

There are many good ideas in DIP32, including this one:

> Basic () syntax, perhaps the cleanest, but can't be used:
> 
> ----
> import std.stdio, std.algorithm, std.container, std.array;
> 
> auto encode(T)(Group!("a == b", T[]) sf) {
>     auto heap = sf.map!((c, f) => (f, [(c, "")])).array.heapify!q{b < a};
> 
>     while (heap.length > 1) {
>         auto (lof, loa) = heap.front;  heap.removeFront;
>         auto (hif, hia) = heap.front;  heap.removeFront;
>         foreach ((_, ref e); loa) e = '0' ~ e;
>         foreach ((_, ref e); hia) e = '1' ~ e;
>         heap.insert((lof + hif, loa ~ hia));
>     }
>     return heap.front[1].schwartzSort!((c, e) => (e.length, c));
> }
> 
> void main() {
>     auto s = "this is an example for huffman encoding"d;
>     foreach ((c, e); s.dup.sort().release.group.encode)
>         writefln("'%s'  %s", c, e);
> }
> ---

The reason why back then it seemed as if it "can't be used" is that it was taken by the comma operator. This is no longer the case.
October 05, 2017
On Thursday, 5 October 2017 at 19:55:15 UTC, Timon Gehr wrote:
>
> The reason why back then it seemed as if it "can't be used" is that it was taken by the comma operator. This is no longer the case.

Fair enough. I only didn't have an issue with it because I had recalled it when reading the previous DIP.
October 05, 2017
On Thursday, 5 October 2017 at 15:23:26 UTC, Seb wrote:
> I think I can state the opinion of many D users here: I don't mind whether it will be curly braces or round parentheses - the important thing is that we will be able to use it in the foreseeable future :)
All my +1s.  Let's leave syntax details to people who know the D grammar inside out.
October 06, 2017
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?

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.

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, but there would be no ambiguity if you used something other than current punctuation to surround the tuple.

Angle brackets come to mind <a>. Also you could use a leading symbol to change the meaning of the parentheses, like $(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).

In your second example, where foo takes a 2-element tuple or 2 values, 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? This seems to contradict your example above.

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

-Steve
October 06, 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.)
October 06, 2017
On Friday, 6 October 2017 at 19:31:11 UTC, Timon Gehr wrote:
> 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.
>
> [snip]
> 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.


So under your thinking, the original example should have been something like:

---
auto id(T)(T x){ return x; }

void main(){
    auto a = id(2); // ok, a is 2.
    auto b = id(1,2); // error, b is not single type argument
    auto c = id(1,); // ok, c is 1.
    auto d = id((1,2)); // ok, d is (1,2)
    auto e = id((1,)); // ok, e is (1,)
}
---
October 06, 2017
On 06.10.2017 21:43, jmh530 wrote:
> On Friday, 6 October 2017 at 19:31:11 UTC, Timon Gehr wrote:
>> 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.
>>
>> [snip]
>> 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.
> 
> 
> So under your thinking, the original example should have been something like:
> 
> ---
> auto id(T)(T x){ return x; }
> 
> void main(){
>      auto a = id(2); // ok, a is 2.
>      auto b = id(1,2); // error, b is not single type argument
>      auto c = id(1,); // ok, c is 1.
>      auto d = id((1,2)); // ok, d is (1,2)
>      auto e = id((1,)); // ok, e is (1,)
> }
> ---

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.