digitalmars.D - Proposal: Object/?? Destruction
- aberba (10/20) Oct 04 2017 DIP reminds me of object destruction.
- Ilya Yaroshenko (3/4) Oct 04 2017 ❤❤❤❤❤ I want this syntax, plz! This solves the issue how to
- SrMordred (2/16) Oct 04 2017 +1
- John Colvin (3/24) Oct 04 2017 People often call this "destructuring" or "unpacking" to avoid
- bitwise (3/6) Oct 04 2017 Or "Structured Bindings" ;)
- =?UTF-8?Q?Ali_=c3=87ehreli?= (3/4) Oct 04 2017 Thanks! Now it makes sense. :)
- aberba (2/6) Oct 04 2017 Thats the word I was looking for. Ha ha
- Timon Gehr (9/33) Oct 04 2017 Why curly braces? Multiple function arguments are a form of built-in
- Seb (6/35) Oct 05 2017 I think I can state the opinion of many D users here: I don't
- Timon Gehr (2/27) Oct 05 2017 I'll create a DIP as soon as I can.
- sarn (3/7) Oct 05 2017 All my +1s. Let's leave syntax details to people who know the D
- Steven Schveighoffer (4/9) Oct 05 2017 I know you have an answer for this, but pardon my ignorance. Why isn't
- Timon Gehr (69/81) Oct 05 2017 I indeed have strong opinions on how to do this correctly, as I have
- Steven Schveighoffer (31/92) Oct 06 2017 I guess my question is more in the context of the problem at hand:
- Timon Gehr (84/195) Oct 06 2017 That's not necessarily bad. (When is the last time you have used a
- jmh530 (13/22) Oct 06 2017 So under your thinking, the original example should have been
- Timon Gehr (5/32) Oct 06 2017 No, under my thinking the original example should have been what it was.
- jmh530 (4/9) Oct 06 2017 Hmm, I hadn't realized that you can have multiple sets of
- Steven Schveighoffer (8/156) Oct 06 2017 It is weird to me that a function with 2 parameters is the same as a
- Timon Gehr (31/56) Oct 07 2017 If a function with 2 parameters was the same as a function that takes a
- Steven Schveighoffer (24/77) Oct 08 2017 My questioning comes with this:
- Timon Gehr (55/153) Oct 09 2017 Well, to me it is a bit confusing that this is puzzling to you. Why
- jmh530 (6/12) Oct 09 2017 Singleton tuples would be necessary for any kind of recursion on
- Steven Schveighoffer (35/87) Oct 10 2017 I understand why (int,) is different from int. What I meant was, why
- Timon Gehr (60/147) Oct 12 2017 Because this would require a special-case rule that I had not considered...
- Q. Schroll (45/45) Oct 14 2017 I've thought about tuples and stuff for a while. For tuples, I'll
- sarn (12/14) Oct 14 2017 But in f([1, 2]), it's ambiguous (just by parsing) whether [1, 2]
- Q. Schroll (43/50) Oct 15 2017 It would be a tuple if that's the best match, otherwise
- sarn (29/42) Oct 16 2017 But have you thought through all the implications?
- Q. Schroll (50/97) Oct 29 2017 Yes. No weirdness is being introduced that is not there already.
- Steven Schveighoffer (38/192) Oct 14 2017 Not a problem!
- jmh530 (4/12) Oct 05 2017 The curly bracket syntax looks straight out of DIP 32
- Timon Gehr (4/45) Oct 05 2017 The reason why back then it seemed as if it "can't be used" is that it
- jmh530 (3/6) Oct 05 2017 Fair enough. I only didn't have an issue with it because I had
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)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.
Oct 04 2017
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 2017
On Wednesday, 4 October 2017 at 10:03:56 UTC, aberba wrote:Upon reading this, It triggered an idea.+1On 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)[...]
Oct 04 2017
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.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)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.
Oct 04 2017
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 2017
On 10/04/2017 05:06 AM, John Colvin wrote:People often call this "destructuring"Thanks! Now it makes sense. :) Ali
Oct 04 2017
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 2017
On 04.10.2017 12:03, aberba wrote:Upon reading this, It triggered an idea.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.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)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.
Oct 04 2017
On Thursday, 5 October 2017 at 06:42:14 UTC, Timon Gehr wrote:On 04.10.2017 12:03, aberba 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 :)Upon reading this, It triggered an idea.Why curly braces? Multiple function arguments are a form of built-in tuple, so the syntax should be consistent: auto (success, message) = callVoldemortFunction();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)[...]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 2017
On 05.10.2017 17:23, Seb wrote:I'll create a DIP as soon as I can.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 :)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.+1
Oct 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.
Oct 05 2017
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 2017
On 05.10.2017 17:40, Steven Schveighoffer wrote:On 10/5/17 2:42 AM, Timon Gehr wrote: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.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? -Stevetypeof((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 2017
On 10/5/17 3:42 PM, Timon Gehr wrote:On 05.10.2017 17:40, Steven Schveighoffer wrote:Right, I agree.On 10/5/17 2:42 AM, Timon Gehr wrote: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.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?typeof((a)) should be typeof(a). This is just a parenthesized expression, as in (a+b)*c.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 2017
On 06.10.2017 14:26, Steven Schveighoffer wrote:On 10/5/17 3:42 PM, Timon Gehr wrote:This could be made to compile, but this is not really about tuples.On 05.10.2017 17:40, Steven Schveighoffer wrote:Right, I agree.On 10/5/17 2:42 AM, Timon Gehr wrote: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.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?typeof((a)) should be typeof(a). This is just a parenthesized expression, as in (a+b)*c.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. ...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).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).--- (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,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)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.)--- 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.
Oct 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,) } ---
Oct 06 2017
On 06.10.2017 21:43, jmh530 wrote:On Friday, 6 October 2017 at 19:31:11 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.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 2017
On Friday, 6 October 2017 at 19:51:39 UTC, Timon Gehr wrote:No, under my thinking the original example should have been what it was. Enclosing an expression in an additional set of parentheses does not change its semantics. This is true even if one set of parentheses is part of the function call.Hmm, I hadn't realized that you can have multiple sets of parentheses without error. I was assuming you would treat the second set as a tuple.
Oct 06 2017
On 10/6/17 3:31 PM, Timon Gehr wrote:On 06.10.2017 14:26, Steven Schveighoffer wrote: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.On 10/5/17 3:42 PM, Timon Gehr wrote:This could be made to compile, but this is not really about tuples.On 05.10.2017 17:40, Steven Schveighoffer wrote:Right, I agree.On 10/5/17 2:42 AM, Timon Gehr wrote: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.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?typeof((a)) should be typeof(a). This is just a parenthesized expression, as in (a+b)*c.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. ...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).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).--- (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,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.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. -SteveThis 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.
Oct 06 2017
On 06.10.2017 23:34, Steven Schveighoffer wrote: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?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. ...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.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.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. -SteveThis 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.
Oct 07 2017
On 10/7/17 8:56 PM, Timon Gehr wrote:On 06.10.2017 23:34, 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. 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?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 matchingIt 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. ...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.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.I definitely don't have an answer off hand, but I wouldn't be surprised if this broke at least some code in phobos.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.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 2017
On 09.10.2017 01:20, Steven Schveighoffer wrote:On 10/7/17 8:56 PM, Timon Gehr wrote: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".On 06.10.2017 23:34, 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. ...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 matchingIt 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. ...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 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.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.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.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.)I guess we'll need to try.I definitely don't have an answer off hand, but I wouldn't be surprised if this broke at least some code in phobos. ...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.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.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 :) ...
Oct 09 2017
On Monday, 9 October 2017 at 15:22:35 UTC, Timon Gehr wrote:Singleton tuples might seem pointless, and some languages do not support such single-element tuples, but as we want to support slicing, they should probably exist. (Also, we might want to create a tuple from an AliasSeq, which can be achieved by dropping it into a single-element tuple and letting it auto-expand.)Singleton tuples would be necessary for any kind of recursion on tuples. The way mir.functional.RefTuple works is similar to your AliasSeq tuple. This is because it uses anonymous names for the fields. std.typecons.Tuple assumes field name/Type pairs.
Oct 09 2017
On 10/9/17 11:22 AM, Timon Gehr wrote:On 09.10.2017 01:20, Steven Schveighoffer wrote: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?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.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.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);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.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?OK, this makes sense, yes. -SteveIf 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.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 :) ...
Oct 10 2017
On 10.10.2017 17:05, Steven Schveighoffer wrote:On 10/9/17 11:22 AM, Timon Gehr wrote: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! :)On 09.10.2017 01:20, Steven Schveighoffer wrote: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? ...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.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?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.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. ...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 makes sense, and I think your proposal improves the design.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.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?
Oct 12 2017
I've thought about tuples and stuff for a while. For tuples, I'll use [brackets]. Reasons follow. Homogeneous tuples are repetitions of some single type. We have them today in form of static arrays. We could allow "inhomogeneous arrays" and call them tuples. T[n] is then an alias for [T, T, .., T] with n repititions. In place of a type [T, S] means Tuple!(T, S) and in place of an Object [t, s] means tuple(t, s). Note that D's grammar allows disambiguate types and objects by syntax. A tuple implicitly converts to some other, if the pointwise types do. Bracket literals constitute a separate type that exists in the compiler only. We have that already to make int[2] a = [ 1, 2 ]; not allocate on the heap, but int[] a = [ 1, 2 ]; does. So at first, [ 1, 2.0 ] is of type [int, double]. If you assign it to a double[2], because int -> double, the conversion is no problem. The thing that changes, is when you ask for typeof([ 1, 2.0 ]) directly. Of course, auto tup = [ 1, 2.0 ]; will homogenize the tuple to double[] similar to how it does today. Declaration-decomposition can be done as auto [a, b] = f(x); (non-exlusive) or [auto a, auto b] = f(x); The first one is shorter, the latter one let's you do [int a, auto b] = f(x); So auto [x1, .. xn] is just a shorthand for [auto x1, .. auto xn]. Assignment-decomposition is the same with the types/auto missing. Swap can be done with [a, b] = [b, a]; From the type system, if a tuple literal has only lvalues inside, it is an lvalue, too. Note that there must be some way to handle side effects correctly. The problem is already known from normal assignment. 1-tuples are in a natural way included. int[1] is today different from int. When we have first-class tuples in D, we should not distinguish static arrays from homogeneous tuples. Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]). I find the syntax (1,) for 1-tuples weird. Parenthesis are used only for operator precedence and function calls. They should not be used for tuples -- the 1-tuple case and f(1, 2) vs f((1, 2)) prove that. Parenthesis are a tool of syntax, not semantics. You can never omit brackets in D. Maybe you can use some of that as input for your DIP.
Oct 14 2017
On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]).But in f([1, 2]), it's ambiguous (just by parsing) whether [1, 2] is a tuple literal or a dynamic array literal. You'd need to use a prefix or something to the bracket syntax. E.g., $[1,2]. The dollar sign is just an example, but it might work because currently $ only seems to be used in slice syntax and in inline assembly. I don't think tuple literals are needed in inline assembly, and I think the slice syntax might be okay because (unlike C) D doesn't allow the borked 0[arr] style of indexing, so expressions like arr[0..$[x][y]] are unambiguous. (I.e., $[x][y] is the yth element of a tuple containing x, and not the yth element of the $th element of x as an array).
Oct 14 2017
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: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.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. [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 2017
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: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).On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:It would be a tuple if that's the best match, otherwise conversion to int[] is tried. ...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. [snip]I just argued, you don't!
Oct 16 2017
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: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.On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:But have you thought through all the implications?On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:It would be a tuple if that's the best match, otherwise conversion to int[] is tried. ...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. [snip]I just argued, you don't!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.orgThis 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=17951To 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 2017
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:Not a problem!On 10/9/17 11:22 AM, Timon Gehr wrote: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! :)On 09.10.2017 01:20, Steven Schveighoffer wrote: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? ...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.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.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).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.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,)).Yes, that seems reasonable to me.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?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 (,)? -SteveI 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.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. ...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);
Oct 14 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"};.
Oct 05 2017
On 05.10.2017 17:48, jmh530 wrote:On Thursday, 5 October 2017 at 06:42:14 UTC, Timon Gehr wrote:There are many good ideas in DIP32, including this one: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"};.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 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.
Oct 05 2017