digitalmars.D - Thoughts on possible tuple semantics
- Dicebot (118/121) Aug 21 2013 Inspired by recent syntax thread I decided to write down by
- deadalnix (16/52) Aug 21 2013 First, thank you so much ! The previous discussion was going
- Dicebot (18/48) Aug 21 2013 My attitude is as follows "either we unify them even at cost of
- deadalnix (5/12) Aug 21 2013 My concern here is to keep the foo(T...)(T args) equivalent to
- Dicebot (11/25) Aug 21 2013 As far as I see it, equivalence will persist. However, this will
- Dicebot (4/6) Aug 21 2013 Also what do you thing about core idea behind proposal - defining
- deadalnix (3/10) Aug 21 2013 ctseq(int) should be the type of the unpacked tuple, to keep
- John Colvin (38/160) Aug 21 2013 I like it :)
- Meta (43/107) Aug 21 2013 One thing I'd like to know. If we're at the point where we're
- Dicebot (30/86) Aug 21 2013 There is a single huge use case - all templates :) If f we don't
- Andrei Alexandrescu (19/33) Aug 21 2013 [snip]
- Dicebot (24/44) Aug 21 2013 It is not easier, it is returning to current issue I was trying
- deadalnix (63/63) Aug 22 2013 So, I took some time to think about this. Let me propose
- Dicebot (9/14) Aug 22 2013 1) You propose typeof to result "not a type" entity. Well, if it
- deadalnix (13/27) Aug 22 2013 It is a sequence of types. It can be used to declare a sequence
- Dicebot (8/22) Aug 22 2013 That implies defining "sequence of X" as special entity on
- deadalnix (8/16) Aug 22 2013 One is a tuple, the other is a sequence. They are different beast
- Dicebot (14/25) Aug 22 2013 I am confused. You proposed tuple implementation is essentially
- deadalnix (5/12) Aug 22 2013 "other than being one possible type of that list element"
- Regan Heath (7/13) Aug 22 2013 I was thinking of it the other way round, as in .. It is not a sequence ...
- H. S. Teoh (97/119) Aug 22 2013 [...]
- deadalnix (20/128) Aug 23 2013 I'd like to see that being allowed explicitly, not implicitly as
- bearophile (12/36) Aug 23 2013 Yes, this is an "obvious" nice feature to support, I didn't list
Inspired by recent syntax thread I decided to write down by vision of how native language tuples should behave and how it may integrate into existing language state. There are lot of tricky corner cases and I'd like to see some more opinions on topic before trying DIP with full deprecation path design. I didn't care about syntax or exact tuple features such as unpacking. Main questions for me were "what this thing actually is? and "how it can be expressed in existing language terms?" ----- Core ----- Define two distinct built-in "tuples": - Template argument sequence: may contain anything that is legal template argument. - Value tuple or simply "tuple": may contain any values, including run-time values. Value storage location is not defined. Template argument sequence that contains only types is called "type sequence" and is considered a type on its own. Type of value tuple is a type sequence. Mixed template argument sequences are considered types too, but special one. Those types can be aliased or passed to templates but can't be instantiated. Each of these two entities has its own literal type. This is required to avoid ambiguity between storing symbol as a type and taking its value on run-time. Tuple always does the latter. In further examples I will use imaginary syntax: ctseq(int, string, 42) : template argument sequence tuple("one", 2, three) : value tuple// connections to existing template syntax void foo(T...)(T args) { static assert(is(T == ctseq(int, string))); static assert(is(typeof(args) == T)); assert(args == tuple(1, "2")); int a = 1; string b = "2"; assert(args == tuple(a, b)); static assert(typeof(tuple(a, b)) == ctseq(int, string)); } foo(1, "2"); // type semantics of type sequence ctseq(int, int) twoVars; twoVars[0] = 42; twovars[1] = 43; assert(twoVars == tuple(42, 43)); ctseq(int, 42) twoVars; // compile-time error, can't instantiate mixed template argument sequence assert(twoVars != ctseq(42, 42)); // NOT the same, breaking change // compile-time vs run-time vs type semantics auto a1 = tuple(42, 42); // ok auto b1 = ctseq(42, 42); // error enum a2_1 = tuple(42, 42); // ok int a, b; enum a2_2 = tuple(a, b); // error enum b2 = ctseq(42, 42); // error, breaking change alias a3 = tuple(42, 42); // error alias b3 = ctseq(42, 42); // ok <<<<< ----- (auto) expansion / packing / unpacking ----- As Andrei has stated clearly that he does not like auto-expansion and considers it a major mistake, I was trying to imagine how that idea can be incorporated into idiomatic D code.// existing syntax // args can't be single entity and use normal parameter passing ABI at the same // time. void foo(T...)(T args) // following normal ABI implies unpacking { } foo(1, 2, 3); // automatic packing <<<<< Breaking something like this does not seem reasonable. But I think salvation is the "..." part. It may be explicitly defined to "implicit unpacking" and can be used with palin tuple code like this:void boo(T)(T args) { foo(args.expand); } boo(tuple(1, 2, 3)); <<<<< One thing to consisder is .tupleof - should it result in actual tuple or maintain current behavior? Former is probably more reasonable but it is even more break struct S { int a, b, c; } foo(S.init.tupleof.expand); // huh .expand should be probably defined as a simple syntax rewrite: - tuple(a, b)" to "a, b" for literals - "tupleVar" to "tupleVar[0], tupleVar[1]" for variables That also implies that packing / unpacking syntax, whatever it can be, is completely unrelated to expansion - former can't be expressed as a simple syntax rewrite, latter can't have special semantics tied to it without creating even more meta-type to represent it. ----- ABI ----- Once built-in value tuples get recognized as a distinct entity, there is no reason to now allow using them for return values or as un-expanded parameters. All is needed is to define that tuple(a, b, c) has same ABI for return values and parameters as a struct Tuple{ typeof(a) a; typeof(b) b; typeof(c) c; } - I have been told that there are some issues with that approach but with no clear explanation. Mangling question remains open. ----- std.typetuple.TypeTuple / std.typecons.Tuple ----- No need to keep them other than for backwards compatibility ;)
Aug 21 2013
First, thank you so much ! The previous discussion was going nowhere as too much focused on syntax. On Wednesday, 21 August 2013 at 15:22:59 UTC, Dicebot wrote:Define two distinct built-in "tuples": - Template argument sequence: may contain anything that is legal template argument. - Value tuple or simply "tuple": may contain any values, including run-time values. Value storage location is not defined. Template argument sequence that contains only types is called "type sequence" and is considered a type on its own. Type of value tuple is a type sequence.Good let's drop tuple for sequence, that is much, much better. However, as sequence works now, it won't fit for the type of a tuple, as it auto expand.Each of these two entities has its own literal type. This is required to avoid ambiguity between storing symbol as a type and taking its value on run-time. Tuple always does the latter. In further examples I will use imaginary syntax: ctseq(int, string, 42) : template argument sequence tuple("one", 2, three) : value tupleI have some syntax suggestion, but let's keep that for later. Semantic first.----- (auto) expansion / packing / unpacking ----- As Andrei has stated clearly that he does not like auto-expansion and considers it a major mistake, I was trying to imagine how that idea can be incorporated into idiomatic D code.That is the hard point, clearly. This has advantages, but difficult to make everything fit together.Which mean that I can call foo with a tuple directly ? What about : auto a = tuple(1, 2); foo(a, 3); I guess this is the pack/unpack issue again.// existing syntax // args can't be single entity and use normal parameter passing ABI at the same // time. void foo(T...)(T args) // following normal ABI implies unpacking { } foo(1, 2, 3); // automatic packing----- std.typetuple.TypeTuple / std.typecons.Tuple ----- No need to keep them other than for backwards compatibility ;)Nobody understood them anyway.
Aug 21 2013
On Wednesday, 21 August 2013 at 15:55:01 UTC, deadalnix wrote:Good let's drop tuple for sequence, that is much, much better. However, as sequence works now, it won't fit for the type of a tuple, as it auto expand.My attitude is as follows "either we unify them even at cost of major breakage or is better to not touch it at all and just focus on some usability features, abandoning the hope to remove confusion". Creating new entities while keeping old ones as-is will make situation even worse :(Yes and I actually favor current auto-expansion despite all the issues - it is just easier to define what expansion is in terms of special tuple type semantics then some sort of action. But Andrei seems to be pretty convinced about this and I have accepted this as a given starting point.As Andrei has stated clearly that he does not like auto-expansion and considers it a major mistake, I was trying to imagine how that idea can be incorporated into idiomatic D code.That is the hard point, clearly. This has advantages, but difficult to make everything fit together.Hm, the wording feels wrong now. No, here is the idea: auto a = tuple(1, 2); foo(a, 3); // rejected foo(a.expand, 3); // works "T..." here simply says, "work with T as it is a tuple but it is a normal function argument list in fact". so no packing actually happens here: "foo(1, 2, 3)", my mistake// existing syntax // args can't be single entity and use normal parameter passing ABI at the same // time. void foo(T...)(T args) // following normal ABI implies unpacking { } foo(1, 2, 3); // automatic packingWhich mean that I can call foo with a tuple directly ? What about : auto a = tuple(1, 2); foo(a, 3); I guess this is the pack/unpack issue again.
Aug 21 2013
On Wednesday, 21 August 2013 at 16:19:43 UTC, Dicebot wrote:Hm, the wording feels wrong now. No, here is the idea: auto a = tuple(1, 2); foo(a, 3); // rejected foo(a.expand, 3); // works "T..." here simply says, "work with T as it is a tuple but it is a normal function argument list in fact". so no packing actually happens here: "foo(1, 2, 3)", my mistakeMy concern here is to keep the foo(T...)(T args) equivalent to template foo(T...) { foo(T args) } We also need to be more precise about what we mean when saying packing/unpacking.
Aug 21 2013
On Wednesday, 21 August 2013 at 16:51:18 UTC, deadalnix wrote:On Wednesday, 21 August 2013 at 16:19:43 UTC, Dicebot wrote:As far as I see it, equivalence will persist. However, this will change: template foo(T...) { void foo() { T values; // error, use "ctseq(T) values" instead, T... can't act as a single entity } }Hm, the wording feels wrong now. No, here is the idea: auto a = tuple(1, 2); foo(a, 3); // rejected foo(a.expand, 3); // works "T..." here simply says, "work with T as it is a tuple but it is a normal function argument list in fact". so no packing actually happens here: "foo(1, 2, 3)", my mistakeMy concern here is to keep the foo(T...)(T args) equivalent to template foo(T...) { foo(T args) } We also need to be more precise about what we mean when saying packing/unpacking.
Aug 21 2013
On Wednesday, 21 August 2013 at 15:55:01 UTC, deadalnix wrote:First, thank you so much ! The previous discussion was going nowhere as too much focused on syntax.Also what do you thing about core idea behind proposal - defining strict type system relation that typeof(tuple(int.init)) == ctseq(int)?
Aug 21 2013
On Wednesday, 21 August 2013 at 16:35:04 UTC, Dicebot wrote:On Wednesday, 21 August 2013 at 15:55:01 UTC, deadalnix wrote:ctseq(int) should be the type of the unpacked tuple, to keep thing flowing with foo(T...)(T args);First, thank you so much ! The previous discussion was going nowhere as too much focused on syntax.Also what do you thing about core idea behind proposal - defining strict type system relation that typeof(tuple(int.init)) == ctseq(int)?
Aug 21 2013
On Wednesday, 21 August 2013 at 15:22:59 UTC, Dicebot wrote:Inspired by recent syntax thread I decided to write down by vision of how native language tuples should behave and how it may integrate into existing language state. There are lot of tricky corner cases and I'd like to see some more opinions on topic before trying DIP with full deprecation path design. I didn't care about syntax or exact tuple features such as unpacking. Main questions for me were "what this thing actually is? and "how it can be expressed in existing language terms?" ----- Core ----- Define two distinct built-in "tuples": - Template argument sequence: may contain anything that is legal template argument. - Value tuple or simply "tuple": may contain any values, including run-time values. Value storage location is not defined. Template argument sequence that contains only types is called "type sequence" and is considered a type on its own. Type of value tuple is a type sequence. Mixed template argument sequences are considered types too, but special one. Those types can be aliased or passed to templates but can't be instantiated. Each of these two entities has its own literal type. This is required to avoid ambiguity between storing symbol as a type and taking its value on run-time. Tuple always does the latter. In further examples I will use imaginary syntax: ctseq(int, string, 42) : template argument sequence tuple("one", 2, three) : value tupleI like it :) The question of ABI is interesing. I can think of a few options, working relative to the System V x86_64 ABI (read C on linux x64) as it's what i'm familiar with: treat tuples as structs: advantages: simple to implement, easy to interact with other ABI compliant code. disadvantages: when returning tuple > 8 bytes requires using up an extra register on the *calling* side as a struct return is done via a pointer in EDI (i.e. 1st argument) to caller-allocated stack memory. This introduces an extra indirection. It's not the fastest option. Same for when passing them. treat tuples as seperate arguments (when possible): This would mean defining a new ABI on the returning side. If we used something like the System V *calling* ABI, then we'd get: advantages: no indirection, arguments are ready in registers for callee, results are ready in registers for the caller to access quickly. Fast. Manu suggests that the advantages are greater on non-x86 disadvantages: not compatible with other ABI compliant code. Increased register pressure for both caller and callee in some circumstances. Could requires some extra movs from stack to registers on caller side, dependent on where the tuple is previously stored/later needed. A consideration for all of this: I predict we would quickly start seeing a lot of code that takes a tuple and returns a modified version. Using the struct option: if larger than 8 bytes, pass pointer to old tuple-struct and new one. Return address of new one in RAX. Indirection but not too bad. Using the seperate option: pass tuple members in seperate registers (when small enough). If they're already in registers and the callee doesn't have to move them out, this couldn't be any faster.* Could (v. rarely these days) result in arguments overflowing on to stack. *movs between registers are essentially free on modern x86/64// connections to existing template syntax void foo(T...)(T args) { static assert(is(T == ctseq(int, string))); static assert(is(typeof(args) == T)); assert(args == tuple(1, "2")); int a = 1; string b = "2"; assert(args == tuple(a, b)); static assert(typeof(tuple(a, b)) == ctseq(int, string)); } foo(1, "2"); // type semantics of type sequence ctseq(int, int) twoVars; twoVars[0] = 42; twovars[1] = 43; assert(twoVars == tuple(42, 43)); ctseq(int, 42) twoVars; // compile-time error, can't instantiate mixed template argument sequence assert(twoVars != ctseq(42, 42)); // NOT the same, breaking change // compile-time vs run-time vs type semantics auto a1 = tuple(42, 42); // ok auto b1 = ctseq(42, 42); // error enum a2_1 = tuple(42, 42); // ok int a, b; enum a2_2 = tuple(a, b); // error enum b2 = ctseq(42, 42); // error, breaking change alias a3 = tuple(42, 42); // error alias b3 = ctseq(42, 42); // ok <<<<< ----- (auto) expansion / packing / unpacking ----- As Andrei has stated clearly that he does not like auto-expansion and considers it a major mistake, I was trying to imagine how that idea can be incorporated into idiomatic D code.// existing syntax // args can't be single entity and use normal parameter passing ABI at the same // time. void foo(T...)(T args) // following normal ABI implies unpacking { } foo(1, 2, 3); // automatic packing <<<<< Breaking something like this does not seem reasonable. But I think salvation is the "..." part. It may be explicitly defined to "implicit unpacking" and can be used with palin tuple code like this:void boo(T)(T args) { foo(args.expand); } boo(tuple(1, 2, 3)); <<<<< One thing to consisder is .tupleof - should it result in actual tuple or maintain current behavior? Former is probably more reasonable but it is even more break struct S { int a, b, c; } foo(S.init.tupleof.expand); // huh .expand should be probably defined as a simple syntax rewrite: - tuple(a, b)" to "a, b" for literals - "tupleVar" to "tupleVar[0], tupleVar[1]" for variables That also implies that packing / unpacking syntax, whatever it can be, is completely unrelated to expansion - former can't be expressed as a simple syntax rewrite, latter can't have special semantics tied to it without creating even more meta-type to represent it. ----- ABI ----- Once built-in value tuples get recognized as a distinct entity, there is no reason to now allow using them for return values or as un-expanded parameters. All is needed is to define that tuple(a, b, c) has same ABI for return values and parameters as a struct Tuple{ typeof(a) a; typeof(b) b; typeof(c) c; } - I have been told that there are some issues with that approach but with no clear explanation. Mangling question remains open. ----- std.typetuple.TypeTuple / std.typecons.Tuple ----- No need to keep them other than for backwards compatibility ;)
Aug 21 2013
On Wednesday, 21 August 2013 at 15:22:59 UTC, Dicebot wrote:- Template argument sequence: may contain anything that is legal template argument.One thing I'd like to know. If we're at the point where we're discussing possible breaking changes to the language anyway, why is it so important that these tuples behave exactly like template argument lists? How important is it to be able to include both types AND values within the same tuple outside of variadic templates? I don't know if I've ever seen a use case for this.- Value tuple or simply "tuple": may contain any values, including run-time values. Value storage location is not defined.Are you implying that the address of a tuple cannot be taken?Template argument sequence that contains only types is called "type sequence" and is considered a type on its own. Type of value tuple is a type sequence.I like this. Just to be clear, can you assign a type sequence to a variable or only alias it, like with regular types?Mixed template argument sequences are considered types too, but special one. Those types can be aliased or passed to templates but can't be instantiated.What is the type of (42, string, dchar, false)? Is it (int, string, dchar, bool) (i.e., type sequence), or is it its own distinct type? Also, what operations can be done on a mixed tuple? Does this count as instantiation?: void foo(T...)(T t) //Error? { //... }Each of these two entities has its own literal type. This is required to avoid ambiguity between storing symbol as a type and taking its value on run-time. Tuple always does the latter.You're losing me. I sort of agree as to why, but I don't the idea of having two different tuples syntaces. That's part of why the situation is as it is in the first place. Yes, there needs to be a good way to make clear which type of tuple you're using, but I don't think two ways of making a tuple is a good thing.void foo(T...)(T args)Looks like my earlier question was answered. Why is this not instantiation?{ static assert(is(T == ctseq(int, string))); static assert(is(typeof(args) == T)); assert(args == tuple(1, "2")); int a = 1; string b = "2"; assert(args == tuple(a, b)); static assert(typeof(tuple(a, b)) == ctseq(int, string)); }Something else. How does tuple(1, "a") relate to ctuple(1, "a"), if at all? How does ctuple(int, string) relate to ctuple(1, "a")? Is there ever a case where typeof(tuple(value, value)) == ctuple(value, value), or ctuple(type, type) == ctuple(value, value)?// type semantics of type sequence ctseq(int, int) twoVars; twoVars[0] = 42; twovars[1] = 43; assert(twoVars == tuple(42, 43)); ctseq(int, 42) twoVars; // compile-time error, can't instantiate mixed template argument sequence assert(twoVars != ctseq(42, 42)); // NOT the same, breaking changeOkay, one question answered.// compile-time vs run-time vs type semantics auto a1 = tuple(42, 42); // ok auto b1 = ctseq(42, 42); // errorGood, but second example will probably cause confusion.enum a2_1 = tuple(42, 42); // ok int a, b; enum a2_2 = tuple(a, b); // errorMakes perfect sense.enum b2 = ctseq(42, 42); // error, breaking changeWill probably cause confusion.alias a3 = tuple(42, 42); // error alias b3 = ctseq(42, 42); // okGreat.One thing to consisder is .tupleof - should it result in actual tuple or maintain current behavior? Former is probably more reasonable but it is even more breakI think it should be consistent with how your hypothetical tuple semantics work in the other situations you described. That would be much less surprising than having only .tupleof auto-expand.struct S { int a, b, c; } foo(S.init.tupleof.expand); // huhI don't see the "huh" here. Seems perfectly reasonable to me. Any code relying on this behaviour would break if members were added to S anyway (and code shouldn't rely on this, IMO)..expand should be probably defined as a simple syntax rewrite: - tuple(a, b)" to "a, b" for literals - "tupleVar" to "tupleVar[0], tupleVar[1]" for variables That also implies that packing / unpacking syntax, whatever it can be, is completely unrelated to expansion - former can't be expressed as a simple syntax rewrite, latter can't have special semantics tied to it without creating even more meta-type to represent it.I don't know what you mean here.----- std.typetuple.TypeTuple / std.typecons.Tuple ----- No need to keep them other than for backwards compatibility ;)Wouldn't both break with the changes you described?
Aug 21 2013
On Wednesday, 21 August 2013 at 16:53:12 UTC, Meta wrote:...Thanks for detailed response! :)On Wednesday, 21 August 2013 at 15:22:59 UTC, Dicebot wrote:There is a single huge use case - all templates :) If f we don't provide built-in way to express parameters accepted by templates, TypeTuple will appear again, because some way to keep and manipulate those parameters is a cornerstone for generic programming. Just count how many times template variadic parameters get sliced (and then aliased or passed to other template) in Phobos.- Template argument sequence: may contain anything that is legal template argument.One thing I'd like to know. If we're at the point where we're discussing possible breaking changes to the language anyway, why is it so important that these tuples behave exactly like template argument lists? How important is it to be able to include both types AND values within the same tuple outside of variadic templates? I don't know if I've ever seen a use case for this.Yes.- Value tuple or simply "tuple": may contain any values, including run-time values. Value storage location is not defined.Are you implying that the address of a tuple cannot be taken?Own distinct type.Mixed template argument sequences are considered types too, but special one. Those types can be aliased or passed to templates but can't be instantiated.What is the type of (42, string, dchar, false)? Is it (int, string, dchar, bool) (i.e., type sequence), or is it its own distinct type?Also, what operations can be done on a mixed tuple? Does this count as instantiation?: void foo(T...)(T t) //Error? { //... }Yes, it is an error if T is not pure type sequence (it already is).You're losing me. I sort of agree as to why, but I don't the idea of having two different tuples syntaces. That's part of why the situation is as it is in the first place.It has played minor role in current situation. Similar naming and vague meaning of TypeTuple has played major roles. Those have different literals because theu have _completely_ different usage semantics. Once those are defined and clear it is no more confusing than static array vs dynamic array.It is :)void foo(T...)(T args)Looks like my earlier question was answered. Why is this not instantiation?Something else. How does tuple(1, "a") relate to ctuple(1, "a"), if at all?tuple(1, "a") == tuple(ctseq(1, "a").expand) // only relation I can think ofHow does ctuple(int, string) relate to ctuple(1, "a")?Completely unrelated.Is there ever a case where typeof(tuple(value, value)) == ctuple(value, value), or ctuple(type, type) == ctuple(value, value)?No, never. Great.I have changed my mind while was typing this answer :) Those may be more related than I have initially expected but I can't express it right now..expand should be probably defined as a simple syntax rewrite: - tuple(a, b)" to "a, b" for literals - "tupleVar" to "tupleVar[0], tupleVar[1]" for variables That also implies that packing / unpacking syntax, whatever it can be, is completely unrelated to expansion - former can't be expressed as a simple syntax rewrite, latter can't have special semantics tied to it without creating even more meta-type to represent it.I don't know what you mean here.Those can be re-implemented to keep old behavior during deprecation period but with all the breakage I don't know if makes any sense/----- std.typetuple.TypeTuple / std.typecons.Tuple ----- No need to keep them other than for backwards compatibility ;)Wouldn't both break with the changes you described?
Aug 21 2013
On 8/21/13 8:22 AM, Dicebot wrote:Inspired by recent syntax thread I decided to write down by vision of how native language tuples should behave and how it may integrate into existing language state.Awesome!Define two distinct built-in "tuples":[snip]In further examples I will use imaginary syntax: ctseq(int, string, 42) : template argument sequence tuple("one", 2, three) : value tupleThere's going to be significant difficulty with this. This would be much easier: static assert(is(ctseq(T) == ctseq(int, string))); Right now "T" as above has no type of its own, and ascribing a type to it would be a major change to which I think it's impossible to retrofit existing code without some breakages. If "T" is a type then "args" has a type and then we're back to asking ourselves whether writeln(args) passes one argument or more to writeln. I do agree that we could take a different route if we designed the type system today, but we aren't. Current design is a legacy/liability.// connections to existing template syntax void foo(T...)(T args) { static assert(is(T == ctseq(int, string)));static assert(is(typeof(args) == T)); assert(args == tuple(1, "2"));So it seems the relationship between ctseq and tuple is that typeof(tuple(x, y. z)) is ctseq(typeof(x), typeof(y), typeof(z)). This leaves out of the discussion things like ctseq(1, "2") which contain compile-time values, not types. In that regard I'd prefer the two being entirely distinct. Andrei
Aug 21 2013
On Wednesday, 21 August 2013 at 19:18:52 UTC, Andrei Alexandrescu wrote:It is not easier, it is returning to current issue I was trying to address - question "what is T and how it is related to existing type system".void foo(T...)(T args) { static assert(is(T == ctseq(int, string)));There's going to be significant difficulty with this. This would be much easier: static assert(is(ctseq(T) == ctseq(int, string)));Right now "T" as above has no type of its ownThen why it can be aliased?If "T" is a type then "args" has a type and then we're back to asking ourselves whether writeln(args) passes one argument or more to writeln.If we assume they auto-expansion is out, it is straightforward: writeln(args) passes one argument, writeln(args.expand) - multiple.I do agree that we could take a different route if we designed the type system today, but we aren't. Current design is a legacy/liability.Then, as I have already said, we can hardly do anything better than renaming TypeTuple and abandoning any hope to remove confusion. Well, maybe one can add some syntax sugar to make it more powerful, but built-ins will remain same weirdos. And this is a time to step down for a moment and ask "is cumulative cost of documentation efforts and learning issues during all D lifespan worse than one hard by finite deprecation process?". Current design is so hacky that we can't really do anything about it without breakage.They can't be distinct as both are accepted in one template argument list and this mandates that both must be able to be aliased. That makes them types. In my proposal ctseq(1, "2") also becomes a type, one that contains only meta-data and can't be instantiated.static assert(is(typeof(args) == T)); assert(args == tuple(1, "2"));So it seems the relationship between ctseq and tuple is that typeof(tuple(x, y. z)) is ctseq(typeof(x), typeof(y), typeof(z)). This leaves out of the discussion things like ctseq(1, "2") which contain compile-time values, not types. In that regard I'd prefer the two being entirely distinct.
Aug 21 2013
So, I took some time to think about this. Let me propose something close, but different. I'll try to define sequences more precisely and define some tools that allow to implement tuples in a nice way on top of it. A sequence is an ordered set. You can have types, alias and values sequences. Values sequence can be runtime or compile time. You can ask for sequences as parameter template using ... and using the regular template parameter syntax. template(T) => template(T...) template(alias T) => template(alias T...) template(T U, T) => template(T U, T...) template(T alias U) => template(T alias U..., T...) Obviously, we want template(T...) to be a special for a transition period. T... being a subset of alias T..., we can start with a warning when the use it outside the subset. It is a breaking change, but reduce the confusion by reusing the template parameter mechanism. Considering that most people here are very confused by type tuples, I highly doubt a lot of code is outside abusing this feature. A type sequence can be used to define a value sequence : auto foo(T...)(T args) { ... } args is a value sequence. It isn't packed as a struct or anything, simply several values (here several function parameters) hidden behing one identifier. As a result, args do not have address or type. typeof(args) returns T, which is a type sequence (not a type !) typeid(args) is invalid. Each member of args however have an address, a type and everything a function parameter have. A sequence provide an access to its member via [index] and length to indicate its length. index need to be a compile time value, and length is a compile time value. It is possible to use a sequence to define any ordered set of stuff (value sequence can be used for function call, type sequence to create multiple fields in a struct, etc . . .). Right, most of what is described here is close from what we already have. Types sequence in a struct can be used to create tuples and IFTI can make it very handy. Sequences can be built using the coma operator. int, float is a type sequence. args[0], args[1] is a value sequence. They can be returned from functions : int, int foo() { return 3, 4; } int, int a = foo(); However, due to syntax conflict, int, int a = 3, 4; is not possible (it the value on the right is an assignment, it conflict with multiple declarations). () can be used to disambiguate : int, int a = (3, 4); The missing piece is an auto dispatch function. I propose here a simple rewrite rule, as this is simple to implement and can be really effective. auto (a, b) = foo(); is rewritten as auto tmp = foo(); // Here tmp is a sequence of declaration of value. No need to create lvalues (especially is a complex copy is involved). assert(tmp.length == 2); // Should be removed anyway for sequences. auto a = tmp[0]; auto b = tmp[1]; This allow to make any user type unpackable. Or even arrays, slices, randomAccessRanges, etc . . .
Aug 22 2013
On Thursday, 22 August 2013 at 11:19:55 UTC, deadalnix wrote:A sequence is an ordered set. You can have types, alias and values sequences. Values sequence can be runtime or compile time.typeof(args) returns T, which is a type sequence (not a type !) typeid(args) is invalid.1) You propose typeof to result "not a type" entity. Well, if it can be result of typeof, can be used to declare variables and can be aliased - what makes it different from a type? 2) is(typeof(tuple(42, 42)) == typeof(ctseq(42, 42))) == ? (assuming typeof(args) == T and you don't make distinction between runtime and compile-time value sequences. Also you don't seem to cover mixed sequences which are essential to D templates.
Aug 22 2013
On Thursday, 22 August 2013 at 12:50:06 UTC, Dicebot wrote:On Thursday, 22 August 2013 at 11:19:55 UTC, deadalnix wrote:It is a sequence of types. It can be used to declare a sequence of variables.A sequence is an ordered set. You can have types, alias and values sequences. Values sequence can be runtime or compile time.typeof(args) returns T, which is a type sequence (not a type !) typeid(args) is invalid.1) You propose typeof to result "not a type" entity. Well, if it can be result of typeof, can be used to declare variables and can be aliased - what makes it different from a type?2) is(typeof(tuple(42, 42)) == typeof(ctseq(42, 42))) == ? (assuming typeof(args) == T and you don't make distinction between runtime and compile-time value sequences.In my proposal, tuple are a library construct. The proposal introduce the necessary tooling to implement them nicely. is(typeof(42, 42) == (int, int)); is(typeof(tuple(42, 42)) == <a library defined struct>); It could be defined as follow : struct Tuple(T...) { T expand; alias expand this; }Also you don't seem to cover mixed sequences which are essential to D templates.Theses are alias sequences.
Aug 22 2013
On Thursday, 22 August 2013 at 13:01:51 UTC, deadalnix wrote:It is a sequence of types. It can be used to declare a sequence of variables.That implies defining "sequence of X" as special entity on official spec and update 'alias', 'typeof' and template docs to reference it as a special case.In my proposal, tuple are a library construct. The proposal introduce the necessary tooling to implement them nicely. is(typeof(42, 42) == (int, int)); is(typeof(tuple(42, 42)) == <a library defined struct>); It could be defined as follow : struct Tuple(T...) { T expand; alias expand this; }It is exactly what we have right now. So you think having two different types of expression/value tuples is fine?As I have already said, it is no less confusing to classify as alias sequence something that is not limited to aliases.Also you don't seem to cover mixed sequences which are essential to D templates.Theses are alias sequences.
Aug 22 2013
On Thursday, 22 August 2013 at 14:03:13 UTC, Dicebot wrote:It is exactly what we have right now. So you think having two different types of expression/value tuples is fine?One is a tuple, the other is a sequence. They are different beast and have different behavior. The fact that it is similar to what we have now is on purpose, so we don't need to break a lot of code. Plus, with the proposed addition, it allow for nice library tuples.It is what alias parameter for templates are. Simply keeping the terminology here.As I have already said, it is no less confusing to classify as alias sequence something that is not limited to aliases.Also you don't seem to cover mixed sequences which are essential to D templates.Theses are alias sequences.
Aug 22 2013
On Thursday, 22 August 2013 at 14:22:50 UTC, deadalnix wrote:One is a tuple, the other is a sequence. They are different beast and have different behavior. The fact that it is similar to what we have now is on purpose, so we don't need to break a lot of code. Plus, with the proposed addition, it allow for nice library tuples.I am confused. You proposed tuple implementation is essentially the very same current std.typecons.Tuple is. So I can see only two differences between value sequence and tuple: 1) latter has address and ABI 2) former auto-expands Anything else?Wait, what? Alias parameters for templates have nothing to do with template argument lists (other than being one possible type of that list element). In other words, "T..." can contain more stuff than "alias T..." (imaginary syntax). And, considering the fact that we already have two different alias semantics (for template alias parameters and normal aliases), it is a dangerous terminology to chose anyway.It is what alias parameter for templates are. Simply keeping the terminology here.Theses are alias sequences.As I have already said, it is no less confusing to classify as alias sequence something that is not limited to aliases.
Aug 22 2013
On Thursday, 22 August 2013 at 14:29:50 UTC, Dicebot wrote:Wait, what? Alias parameters for templates have nothing to do with template argument lists (other than being one possible type of that list element). In other words, "T..." can contain more stuff than "alias T..." (imaginary syntax)."other than being one possible type of that list element" Indeed it is the only possible meaning and it the right one.And, considering the fact that we already have two different alias semantics (for template alias parameters and normal aliases), it is a dangerous terminology to chose anyway.Yes, the term have 2 meanings. I simply reuse one of theses meaning without adding a new one.
Aug 22 2013
On Thu, 22 Aug 2013 15:03:12 +0100, Dicebot <public dicebot.lv> wrote:I was thinking of it the other way round, as in .. It is not a sequence of aliases, but an alias for a sequence of .. expressions(?). Perhaps calling it an "aliased sequence" makes that clearer? R -- Using Opera's revolutionary email client: http://www.opera.com/mail/As I have already said, it is no less confusing to classify as alias sequence something that is not limited to aliases.Also you don't seem to cover mixed sequences which are essential to D templates.Theses are alias sequences.
Aug 22 2013
On Thursday, 22 August 2013 at 14:27:01 UTC, Regan Heath wrote:On Thu, 22 Aug 2013 15:03:12 +0100, Dicebot <public dicebot.lv> wrote:Well, it falls into the same issue "named after what you can do with it" vs "named after what it is". The very meaning of word "alias" suggests that calling something "alias for X" is just adding naming indirection on top of X ;)I was thinking of it the other way round, as in .. It is not a sequence of aliases, but an alias for a sequence of .. expressions(?). Perhaps calling it an "aliased sequence" makes that clearer? RAs I have already said, it is no less confusing to classify as alias sequence something that is not limited to aliases.Also you don't seem to cover mixed sequences which are essential to D templates.Theses are alias sequences.
Aug 22 2013
On Thursday, 22 August 2013 at 14:32:27 UTC, Dicebot wrote:Well, it falls into the same issue "named after what you can do with it" vs "named after what it is". The very meaning of word "alias" suggests that calling something "alias for X" is just adding naming indirection on top of X ;)That is what an alias parameter is, and I do think that regular alias and alias parameter should converge.
Aug 22 2013
On Thu, Aug 22, 2013 at 01:19:53PM +0200, deadalnix wrote:So, I took some time to think about this. Let me propose something close, but different. I'll try to define sequences more precisely and define some tools that allow to implement tuples in a nice way on top of it.[...] Originally, I typed up a long response to what you posted, but suddenly, it dawned on me that your idea, quoted below, is actually something much more general and applicable than tuples alone. So I decided to dedicate my reply to it:The missing piece is an auto dispatch function. I propose here a simple rewrite rule, as this is simple to implement and can be really effective. auto (a, b) = foo(); is rewritten as auto tmp = foo(); // Here tmp is a sequence of declaration of value. No need to create lvalues (especially is a complex copy is involved). assert(tmp.length == 2); // Should be removed anyway for sequences. auto a = tmp[0]; auto b = tmp[1]; This allow to make any user type unpackable. Or even arrays, slices, randomAccessRanges, etc . . .Now *this* is something new, and worth talking about. Suppose we forget about the whole tuple fiasco, and forget that there's such a thing as a tuple (or TypeTuple or whatever else there is that's confusing everybody). Just with this syntax alone, we can solve all kinds of problems: - If f is a function that returns some kind of array, we can have automatic unpacking of arrays: int[] func() { return [1,2,3]; } void main() { auto (x, y, z) = func(); // Equivalent to: // auto tmp = func(); // x = tmp[0]; // y = tmp[1]; // z = tmp[2]; } - We can automatically unpack regex matches: import std.regex; auto (x, y, z) = inputString.match(`(\d+)\s+(\w+)\s+(\S+)`).captures; // x = string matched by (\d+) // y = string matched by (\w+) // z = string matched by (\S+) - Like you said, any indexable range can be supported by this syntax. In fact, I'd argue that you should be able to do this even with just an input range: auto (x, y, z) = makeInputRange(); should be translated into: auto tmp = makeInputRange(); assert(!tmp.empty); auto x = tmp.front; tmp.popFront(); assert(!tmp.empty); auto y = tmp.front; tmp.popFront(); assert(!tmp.empty); auto z = tmp.front; Since ranges are a major selling feature of D, I'd argue that in-language support should be completely appropriate, and even desirable. (In fact, foreach already understands what a range is, so why not extend it here as well.) You said that the missing piece was an auto dispatch function. Well, I think if we add yet another piece to it, this could become a killer feature in D, even regardless of what happens with the whole tuples issue: The above is all nice and good, except that you can't pass a tuple/array/range return from a function into a poly-adic function's arguments. That is to say: int[] func1() { return [1,2,3]; } void func2(int x, int y, int z) { ... } // Currently this line doesn't compile: func2(func1()); I used int[] for illustration purposes only; it can also be, say, an input range of ints: T func1() { ... } assert(isInputRange!T && is(ElementType!T == int)); void func2(int x, int y, int z) { ... } func2(func1()); // ^^^ this will be rewritten into: // auto tmp = func1(); // auto arg1 = tmp.front; // tmp.popFront(); // auto arg2 = tmp.front; // tmp.popFront(); // auto arg3 = tmp.front; // func2(arg1, arg2, arg3); Of course, if T is an array, then it will be rewritten into func2(tmp[0], tmp[1], tmp[2]); same goes if T is a Tuple, etc.. Anything indexable with array notation should undergo this rewriting. So you could write: Tuple!(int,string,bool) func1() { return tuple(1, "a", true); } void func2(int x, string y, bool z) { ... } func2(func1()); // ^^^^ this gets rewritten into: // auto tmp = func1(); // func2(tmp[0], tmp[1], tmp[2]); My point is that this auto-dispatch / auto-repack is not limited to tuples alone. It can be made to work in a nice way to anything that has array indexing notation or a range interface. This would solve the problem of multiple return values, for example. You could have a div() function that returns a quotient and remainder: auto div(int x, int y) { ... return [q, r]; // Or, (q, r), or a 2-element input range } auto (x, y) = div(13, 7); T -- Computers aren't intelligent; they only think they are.
Aug 22 2013
On Thursday, 22 August 2013 at 19:48:36 UTC, H. S. Teoh wrote:Now *this* is something new, and worth talking about. Suppose we forget about the whole tuple fiasco, and forget that there's such a thing as a tuple (or TypeTuple or whatever else there is that's confusing everybody). Just with this syntax alone, we can solve all kinds of problems: - If f is a function that returns some kind of array, we can have automatic unpacking of arrays: int[] func() { return [1,2,3]; } void main() { auto (x, y, z) = func(); // Equivalent to: // auto tmp = func(); // x = tmp[0]; // y = tmp[1]; // z = tmp[2]; } - We can automatically unpack regex matches: import std.regex; auto (x, y, z) = inputString.match(`(\d+)\s+(\w+)\s+(\S+)`).captures; // x = string matched by (\d+) // y = string matched by (\w+) // z = string matched by (\S+) - Like you said, any indexable range can be supported by this syntax. In fact, I'd argue that you should be able to do this even with just an input range: auto (x, y, z) = makeInputRange(); should be translated into: auto tmp = makeInputRange(); assert(!tmp.empty); auto x = tmp.front; tmp.popFront(); assert(!tmp.empty); auto y = tmp.front; tmp.popFront(); assert(!tmp.empty); auto z = tmp.front; Since ranges are a major selling feature of D, I'd argue that in-language support should be completely appropriate, and even desirable. (In fact, foreach already understands what a range is, so why not extend it here as well.)Yes that is the intended effect.You said that the missing piece was an auto dispatch function. Well, I think if we add yet another piece to it, this could become a killer feature in D, even regardless of what happens with the whole tuples issue: The above is all nice and good, except that you can't pass a tuple/array/range return from a function into a poly-adic function's arguments. That is to say: int[] func1() { return [1,2,3]; } void func2(int x, int y, int z) { ... } // Currently this line doesn't compile: func2(func1()); I used int[] for illustration purposes only; it can also be, say, an input range of ints: T func1() { ... } assert(isInputRange!T && is(ElementType!T == int)); void func2(int x, int y, int z) { ... } func2(func1()); // ^^^ this will be rewritten into: // auto tmp = func1(); // auto arg1 = tmp.front; // tmp.popFront(); // auto arg2 = tmp.front; // tmp.popFront(); // auto arg3 = tmp.front; // func2(arg1, arg2, arg3); Of course, if T is an array, then it will be rewritten into func2(tmp[0], tmp[1], tmp[2]); same goes if T is a Tuple, etc.. Anything indexable with array notation should undergo this rewriting. So you could write: Tuple!(int,string,bool) func1() { return tuple(1, "a", true); } void func2(int x, string y, bool z) { ... } func2(func1()); // ^^^^ this gets rewritten into: // auto tmp = func1(); // func2(tmp[0], tmp[1], tmp[2]); My point is that this auto-dispatch / auto-repack is not limited to tuples alone. It can be made to work in a nice way to anything that has array indexing notation or a range interface. This would solve the problem of multiple return values, for example. You could have a div() function that returns a quotient and remainder: auto div(int x, int y) { ... return [q, r]; // Or, (q, r), or a 2-element input range } auto (x, y) = div(13, 7); TI'd like to see that being allowed explicitly, not implicitly as you propose. foo(int, int, int) must be different than foo(int[3]) . If not, the TypeTuple unpacking mess is taken to yet another level. The good news is that this is implementable with the proposal : int, int, int dispatch(int[3] args) { return args[0], args[1], args[2]; } foo(dispatch(arr)); To sum up, the only major change my proposal got it the ability to return values sequences. That plus some syntaxic sugar provide everything we need. I'm thinking about it for month now and it only poped recently. I'm now convinced that this is the way forward : - If ABI allow multiple return values, we can take advantage of it. - We can have clean tuples. - We give more expressiveness to user defined types.
Aug 23 2013
H. S. Teoh:any indexable range can be supported by this syntax. In fact, I'd argue that you should be able to do this even with just an input range: auto (x, y, z) = makeInputRange(); should be translated into: auto tmp = makeInputRange(); assert(!tmp.empty); auto x = tmp.front; tmp.popFront(); assert(!tmp.empty); auto y = tmp.front; tmp.popFront(); assert(!tmp.empty); auto z = tmp.front; Since ranges are a major selling feature of D, I'd argue that in-language support should be completely appropriate, and even desirable.Yes, this is an "obvious" nice feature to support, I didn't list it because I wasn't bold enough :-) This is a good situation to show how other languages do, this is Python2:<generator object <genexpr> at 0x02230238>lazy = (x * x for x in xrange(1, 4)) lazy1a, b, c = lazy a4b9 Similar code is possible in Haskell and Perl6. Bye, bearophilec
Aug 23 2013