www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Proposal: Object/?? Destruction

reply aberba <karabutaworld gmail.com> writes:
  Upon reading this, It triggered an idea.
 On Saturday, 30 September 2017 at 16:10:44 UTC, Jonathan Marler 
 wrote:
 https://wiki.dlang.org/DIP88

 I'd like to see DIP88 (Named Parameters) revived.  Was this 
 proposal rejected or is it just stale and needs a refresh?  
 Named parameters can be implemented in a library, however, in 
 my opinion they are useful enough to warrant a clean syntax 
 with language support.  I'd be willing to refresh the DIP so 
 long as I know the idea has not already been rejected.
DIP reminds me of object destruction. /* extracts success & message from returned type. Could be tuple or structure, etc. May even eliminate use of tuples for multiple return */ auto {success, message} = callVoldermortFunction(); This is concept is used in Kotlin. JavaScript es6 takes it even further (function parameters and arguments support object destruction)
Oct 04
next sibling parent Ilya Yaroshenko <ilyayaroshenko gmail.com> writes:
On Wednesday, 4 October 2017 at 10:03:56 UTC, aberba wrote:
 auto {success, message} = callVoldermortFunction();
❤❤❤❤❤ I want this syntax, plz! This solves the issue how to return multiple ref values. --Ilya
Oct 04
prev sibling next sibling parent SrMordred <patric.dexheimer gmail.com> writes:
On Wednesday, 4 October 2017 at 10:03:56 UTC, aberba wrote:
  Upon reading this, It triggered an idea.
 On Saturday, 30 September 2017 at 16:10:44 UTC, Jonathan 
 Marler wrote:
 [...]
DIP reminds me of object destruction. /* extracts success & message from returned type. Could be tuple or structure, etc. May even eliminate use of tuples for multiple return */ auto {success, message} = callVoldermortFunction(); This is concept is used in Kotlin. JavaScript es6 takes it even further (function parameters and arguments support object destruction)
+1
Oct 04
prev sibling next sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Wednesday, 4 October 2017 at 10:03:56 UTC, aberba wrote:
  Upon reading this, It triggered an idea.
 On Saturday, 30 September 2017 at 16:10:44 UTC, Jonathan 
 Marler wrote:
 https://wiki.dlang.org/DIP88

 I'd like to see DIP88 (Named Parameters) revived.  Was this 
 proposal rejected or is it just stale and needs a refresh?  
 Named parameters can be implemented in a library, however, in 
 my opinion they are useful enough to warrant a clean syntax 
 with language support.  I'd be willing to refresh the DIP so 
 long as I know the idea has not already been rejected.
DIP reminds me of object destruction. /* extracts success & message from returned type. Could be tuple or structure, etc. May even eliminate use of tuples for multiple return */ auto {success, message} = callVoldermortFunction(); This is concept is used in Kotlin. JavaScript es6 takes it even further (function parameters and arguments support object destruction)
People often call this "destructuring" or "unpacking" to avoid confusion with destructors.
Oct 04
next sibling parent bitwise <bitwise.pvt gmail.com> writes:
On Wednesday, 4 October 2017 at 12:06:43 UTC, John Colvin wrote:
 [...]

 People often call this "destructuring" or "unpacking" to avoid 
 confusion with destructors.
Or "Structured Bindings" ;) http://en.cppreference.com/w/cpp/language/structured_binding
Oct 04
prev sibling next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 10/04/2017 05:06 AM, John Colvin wrote:

 People often call this "destructuring"
Thanks! Now it makes sense. :) Ali
Oct 04
prev sibling parent aberba <karabutaworld gmail.com> writes:
On Wednesday, 4 October 2017 at 12:06:43 UTC, John Colvin wrote:
 On Wednesday, 4 October 2017 at 10:03:56 UTC, aberba wrote:
  Upon reading this, It triggered an idea.
 People often call this "destructuring" or "unpacking" to avoid 
 confusion with destructors.
Thats the word I was looking for. Ha ha
Oct 04
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 04.10.2017 12:03, aberba wrote:
   Upon reading this, It triggered an idea.
 On Saturday, 30 September 2017 at 16:10:44 UTC, Jonathan Marler wrote:
 https://wiki.dlang.org/DIP88

 I'd like to see DIP88 (Named Parameters) revived.  Was this proposal 
 rejected or is it just stale and needs a refresh? Named parameters 
 can be implemented in a library, however, in my opinion they are 
 useful enough to warrant a clean syntax with language support.  I'd 
 be willing to refresh the DIP so long as I know the idea has not 
 already been rejected.
DIP reminds me of object destruction. /* extracts success & message from returned type. Could be tuple or structure, etc. May even eliminate use of tuples for multiple return */ 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(); 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.
Oct 04
next sibling parent reply Seb <seb wilzba.ch> writes:
On Thursday, 5 October 2017 at 06:42:14 UTC, Timon Gehr wrote:
 On 04.10.2017 12:03, aberba wrote:
   Upon reading this, It triggered an idea.
 On Saturday, 30 September 2017 at 16:10:44 UTC, Jonathan 
 Marler wrote:
 [...]
DIP reminds me of object destruction. /* extracts success & message from returned type. Could be tuple or structure, etc. May even eliminate use of tuples for multiple return */ 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
Oct 05
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 05
prev sibling parent sarn <sarn theartofmachinery.com> writes:
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.
Oct 05
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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. Why isn't (a) good enough? -Steve
Oct 05
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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,) } ---
Oct 05
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
Oct 06
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.)
Oct 06
next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
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,) } ---
Oct 06
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 06
parent jmh530 <john.michael.hall gmail.com> writes:
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.
Oct 06
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
Oct 06
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 07
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
Oct 08
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 09
next sibling parent jmh530 <john.michael.hall gmail.com> writes:
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.
Oct 09
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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
Oct 10
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 12
next sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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.
Oct 14
parent reply sarn <sarn theartofmachinery.com> writes:
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).
Oct 14
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:
 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.
It would be a tuple if that's the best match, otherwise conversion to int[] is tried. Even today, [1, 2] is ambiguous: Is it a static or a dynamic array of int? Is it of type int[2] or int[]? The spec says, it depends what you do with it! We can progress that and enlarge the int[2] version to [int, int] -- a special case of a 2-tuple. It remains the same: If [1, 2] can be used as a dynamic array, it will be. If not, the compiler tries a static array. With tuples, it would try a tuple. If f has an overload taking int[] or something similar, it will treat [1, 2] as a dynamic array with homogeneus types. If the objects are not compatible, an error occurs like "tuple [..contents..] cannot be implicitly converted to T[]". Else, if it has an overload for a compatible (length, implicit conversion) tuple, that will be taken. Consider void f(int[2] v) { } // (1) void f(int[ ] v) { } // (2) Here, f([1, 2]) calls (1) as it is the better match. Yet with auto x = [1, 2]; f(x) calls (2) because of strict typing. So while [1, 2] is of type int[2] or [int, int] as a tuple, typeof([1, 2]) will still yield int[]. You cannot ask the one and only correct type of []-literals as they have more than one type. Even if the values are incompatible like [1, "a"], asking typeof([1, "a"]) will result in an error, because in typeof deduction, []-literals must result in dynamic arrays. This holds for auto, because auto has the same rules. auto tup = [1, "a"]; must fail. You'd need [auto, auto] tup = [1, "a"]; or maybe some shorthand syntax that lowers to this.
 You'd need to use a prefix or something to the bracket syntax.
 [snip]
I just argued, you don't! The reason there is no such prefix and not even a function in Phobos, is it is a trivial task to make one. T[n] s(T, size_t n)(T[n] elem ...) { return elem; } static assert(is(typeof(s(1, 2, 3)) == int[3])); static assert(is(typeof([1, 2, 3].s) == int[3])); auto x = s(1, 2, 3); static assert(is(typeof(x) == int[3])); auto x = s(1, 2.0, 3); static assert(is(typeof(x) == double[3])); Try it yourself. It works fine. Instead of s one would use t or tuple to allow incompatible types.
Oct 15
parent reply sarn <sarn theartofmachinery.com> writes:
On Sunday, 15 October 2017 at 15:19:21 UTC, Q. Schroll wrote:
 On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:
 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.
It would be a tuple if that's the best match, otherwise conversion to int[] is tried. ...
 You'd need to use a prefix or something to the bracket syntax.
 [snip]
I just argued, you don't!
But have you thought through all the implications? Take this code: void main(string[] args) { import std.stdio : writeln; writeln([1, 3.14]); } As you're probably 100% aware, this is totally valid D code today. [1, 3.14] becomes a double[] because 1 gets converted to a double. If this kind of behaviour changes, code will break, so you'll need a bunch of exceptions to the "it would be a tuple if that's the best match" rule. Also, for the same backwards compatibility reasons, it would be impractical in most cases to add any tuple overloads to most existing standard library functions that currently accept slices or arrays, but presumably new functions would be meant to take advantage of the new syntax (else there wouldn't be much point creating a new syntax). So, a literal like [1, 3.14] would basically be a tuple, but would be converted to double[] in a bunch of special cases for historical reasons. If you're not sure if this is really a problem, take a look at the confusion caused by the magic in {} syntax: https://forum.dlang.org/thread/ecwfiderxbfqzjcyymkg forum.dlang.org https://forum.dlang.org/thread/ihsmxiplprxwlqkgwswc forum.dlang.org https://forum.dlang.org/thread/qsayoktyffczskrnmgxu forum.dlang.org To be totally honest, I still don't see what's wrong with just creating a new bracket syntax, instead of adding more magic to [] (or () for that matter).
Oct 16
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 16 October 2017 at 23:29:46 UTC, sarn wrote:
 On Sunday, 15 October 2017 at 15:19:21 UTC, Q. Schroll wrote:
 On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:
 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.
It would be a tuple if that's the best match, otherwise conversion to int[] is tried. ...
 You'd need to use a prefix or something to the bracket syntax.
 [snip]
I just argued, you don't!
But have you thought through all the implications?
Yes. No weirdness is being introduced that is not there already. Maybe I have overseen something; I will not give you or anyone else a guarantee for the solution to work perfectly. I've thought through the case very long. An open question is allowing partly const/immutable/shared (cis) tuples. As for now, I didn't care. Even c/i/s-homogeneus tuples (the tuple is c/i/s as a whole or not) would be a win in my opinion. One rarely needs tuples with one component immutable but the other one mutable. This is what a named struct is for. On the other hand, I don't know of any issues having a to partly c/i/s std.typecons.Tuple.
 Take this code:

 void main(string[] args)
 {
     import std.stdio : writeln;
     writeln([1, 3.14]);
 }

 As you're probably 100% aware, this is totally valid D code 
 today.  [1, 3.14] becomes a double[] because 1 gets converted 
 to a double.
Right conclusion with insufficient explanation. [1, 3.14] is a static array in the first place. It occupies a fully inferred template parameter position. I don't know the implementation, but every time I tested, it behaves as if typeof(expr) is being used after the bang to set the template argument manually (even for Voldemort types etc. where typeof is sometimes impossible due to missing frame pointers). typeof returns "dynamic array of T" for array literals. This is all the weirdness going on here. It is present today and would remain present if you interpret [1, 3.14] as a tuple.
 If this kind of behaviour changes, code will break, so you'll 
 need a bunch of exceptions to the "it would be a tuple if 
 that's the best match" rule.
The only exception is typeof and (therefore, I don't know...) template inference.
 Also, for the same backwards compatibility reasons, it would be 
 impractical in most cases to add any tuple overloads to most 
 existing standard library functions that currently accept 
 slices or arrays, but presumably new functions would be meant 
 to take advantage of the new syntax (else there wouldn't be 
 much point creating a new syntax).
You don't have to as long as you don't want to support tuples explicitly; otherwise you have to. If you have a void f(int, double), you cannot plug in [1, 3.14]. You can use some expand to do it. You wouldn't want to either. If you have something *explicitly typed* as a tuple, e.g. [int, double] tup = [1, 3.14]; you can make the call f(tup) because auto expansion does its job. This is the use case. If you have void f([int, double]), you can plug in tuple literals. If you use a tuple literal for a function call, the compiler will search for explicit matches for tuples. If it cannot find any, conversion to a dynamic array happens.
 So, a literal like [1, 3.14] would basically be a tuple, but 
 would be converted to double[] in a bunch of special cases for 
 historical reasons.
Yes. It would be converted in almost all cases -- the same with static arrays -- because the best match doesn't occur very often and typeof never returns static arrays or tuples for literals.
 If you're not sure if this is really a problem, take a look at 
 the confusion caused by the magic in {} syntax:

 https://forum.dlang.org/thread/ecwfiderxbfqzjcyymkg forum.dlang.org
 https://forum.dlang.org/thread/ihsmxiplprxwlqkgwswc forum.dlang.org
 https://forum.dlang.org/thread/qsayoktyffczskrnmgxu forum.dlang.org
This is completely unrelated. Concerning the issues people have with (..) => { .. }, I've filed an enhancement request to deprecate it in that specific case: https://issues.dlang.org/show_bug.cgi?id=17951
 To be totally honest, I still don't see what's wrong with just 
 creating a new bracket syntax, instead of adding more magic to 
 [] (or () for that matter).
It's not adding any magic to [] that isn't there already. The other proposals are adding magic to (). Even some mathematicians use chevrons (angle brackets) for tuples as they see parentheses as indicators of precedence. I'd vote against angle brackets, see C++ templates for reasons. Logicians and haskellers even don't need parentheses for function calls. Could I convince you?
Oct 29
prev sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
Sorry for waiting so long to respond, I had to think about this a lot...

On 10/12/17 3:05 PM, Timon Gehr wrote:
 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! :)
Not a problem!
 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));
Right, there is a structural difference here. The 2 and 3 being tied together is a piece of information that is lost or different than is expected. I get that, and agree with it.
 
 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,)).
This, I'm not 100% sure on. In my mind, the difference between a value and a tuple of one element is trivial and negligible. I think they should be implicitly convertible between each other. Perhaps they would still be mangled differently, but you could still call both with the different forms. Sort of like const(int) and int have different semantics, but I can call a function with one or the other. In that sense, perhaps foo(int) and foo((int,)) are different manglings, but you can still call either form with either form.
 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?
Yes, that seems reasonable to me.
 
 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.
Right, I was specifically focusing on what I understood -- the existing mechanism of "singleton tuple" in D. But really, I think implicit expanding or tupling of values when needed would fix all the problems I had, fits into the current expectations of D users, and allows your scheme to work. So to recap my thoughts: 1. I think T and (T,) can be different types, probably should be mangled differently, but implicitly cast to one another, similar to T and const(T) when T is a value type. 2. I'm OK with fun(T,T) and fun((T,T)) being equivalent. 3. I'm still not in love with (T,) as a singleton tuple type, it looks like you accidentally put in a trailing comma. But I don't have a better syntax to suggest. 4. I'm still convinced that having a template type match a tuple when multiple parameters are passed as in: foo(T)(T t) {...} foo(1, 2); // OK, T = (int, int) is not going to go well with existing code. On point 4, as a compromise, would it be possible to opt-in to such things via a new syntax? For example something like: foo((T,...))(T t) // T is a tuple of 0 or more items. One other thing, what does an empty tuple look like? Is it (,)? -Steve
Oct 14
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
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"};.
Oct 05
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 05
parent jmh530 <john.michael.hall gmail.com> writes:
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.
Oct 05