August 21, 2013
On Wednesday, 21 August 2013 at 00:38:31 UTC, Andrei Alexandrescu wrote:
> I think this whole thread is approaching things wrongly. It should be:
>
> 1. What do we need?
>
> 2. What does a solution look like in the current language?
>
> 3. Which parts of the solution are frequent/cumbersome enough to warrant a language change?

Exactly. Though if we want to avoid breaking changes as much as possible, maybe it should be more like this:

2. What use cases current language state cover?

3. How it can me remodeled to have less confusing semantics / definitions while covering as much old behavior as possible?

I am struggling at (3) because current applications love to contradict each other when any relatively simple rule set is imagined.

Basically there are 3 distinct (according to allowed usage) cases for built-in tuples:

1) pure type-tuple
2) mixed compile-time tuple (literals and other compile-time expressions go here)
3) run-time tuple over set of variables/parameters

As far as I can see, covering std.typecons.Tuple in similar way is trivial - but re-defining those 3 in a meaningful way is really challenging. Maybe you have any opinion as a concept author? :P
August 21, 2013
On Wednesday, 21 August 2013 at 01:50:49 UTC, Dicebot wrote:
> On Wednesday, 21 August 2013 at 00:38:31 UTC, Andrei Alexandrescu wrote:
>> I think this whole thread is approaching things wrongly. It should be:
>>
>> 1. What do we need?

Also my answer to this question may probably be not very mainstream according to this thread :) I thing that most of all we need definition-driven tuple classification as opposed to current behavior-driven one (here is the built-in tuple and look what shiny stuff it can do).

Everything else is just a consequence.

August 21, 2013
On 2013-08-20 20:16:48 +0000, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> said:

> On 8/20/13 6:57 AM, Dicebot wrote:
>> Preserve
>> auto-expansion.
> 
> That would be problematic to say the least. (There have been a few discussions in this group; I've come to think auto expansion is fail.)

I used to like auto expansion, probably because it was convenient for what I was doing. I always feared that not having auto-expansion would make my code ugly.

But now that I've been working with C++11 with its variadic template parameters that don't auto expand, I'm quite fond of it. To expand a "tuple" all you have to do is suffix it with "...", example:

	template < typename A, typename B, typename... C >
	shared_ptr< Expression > make_exp(Operator op, A first, B second, C... more)
	{
		return make_exp(op, make_exp(op, first, second), more...);
	}

Now, if we could get rid of auto expansion while requiring a "..." suffix to expand a tuple, I think it'd be really great. Can't we steal this from C++?

What follows is how I'd transpose this to D.

First, let's put the three-dots *before* the template argument (somewhat as in C++) to get a packed tuple. This will ensure backward compatibility (if you put the three-dots after, like current D, you'd still get an auto-expanding tuple):

	Expression make_exp(A, B, ...C)(Operator op, A first, B second, C... more)
	{
		return make_exp(op, make_exp(op, first, second), more...);
	}

Basically, the rule is simple: three dots before an expression means "pack", three dots after it means "expand".

In the example above, the template argument C is a packed tuple type. Expanding C in the function's argument list means that we're expecting arguments to come as separate argument and not as one packed tuple argument. Those separate arguments get packed into the "more" parameter variable, which is of type C, which is then expanded as requested by the three-dot suffix when calling the function.

Extrapolating things a little, a packed tuple type could be expressed like this:

	...(int, double) x; // a packed type tuple variable
	x = ...(1, 2.3); // create a packed value tuple and assign it to x.
	writeln(x...); // expand tuple to make it two separate arguments
	writeln(x); // keep it packed and you're passing it as one argument

	...(int, ...(double, float)) y; // nested tuples

Also, you can take an auto-expanding tuple and make it packed:

	template (X...)
	{
		...(X) x; // auto-expanding tuple becomes packed
	}

Which is the same as making it packed directly from the argument list:

	template (...X)
	{
		X x; // already packed tuple
	}

And since this is a "language-type" tuple, it can contain a mix of expression, types, aliases, etc, and therefore can be used like this:

	...(int, "ha!", writeln); // can contain anything usable as a template argument

Other examples:

	int x;
	double y;
	...(x, y) = ...(1, 2.3); // multiple assignments
	...(x, y) = getPackedTuple();

And finally, we could support expanding whole expressions that have a packed tuple in them by shifting the expanding three-dot outward, as you can do with C++11:

	...(int, double) x;
	func(exp(x+5)...); // expands to: func(exp(x[0]+5), exp(x[1]+5));

Wouldn't that be great?

After some time, if desired, auto-expanding tuples could be deprecated. The migration path wouldn't be too hard, nor too verbose: just add those three-dots right and left as needed.

-- 
Michel Fortin
michel.fortin@michelf.ca
http://michelf.ca

August 21, 2013
On 2013-08-21 00:38:30 +0000, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> said:

> 1. What do we need?

I think D needs less tuples. It has two kinds (language kind and Phobos's kind), which is confusing. It should have just one that covers most use cases.

So in essence a tuple should:

1. be able to contain anything you can put as a template argument
2. be able to be packed and expanded as needed

And the above should be usable in these situations:

- using a type tuple as a variable's type
- unpacking a tuple as function/template arguments
- using a tuple as a return type
- assigning a value tuple to an alias-of-variables tuples
- using foreach with a tuple
- pass a tuple by reference to a function
- return a tuple by reference from a function
- slice a tuple
- concat tuples

I'm on the fence about taking the address of a tuple. Not being able to take the address has an upside: if the function call ABI pass each component of a tuple as one ref parameter under the hood, then you could pass tuple of aliases as function arguments. For instance, here I'd be swapping variables a, c, and e with variables b, d, and f with just one call to swap:

	int a, b, c, d, e, f;
	swap(...(a, c, e), ...(b, d, f));

(Note: using "..." syntax to create packed tuple literals. See my other post about how that'd work.)

-- 
Michel Fortin
michel.fortin@michelf.ca
http://michelf.ca

August 21, 2013
On Tuesday, 20 August 2013 at 18:51:23 UTC, Andrei Alexandrescu wrote:
> On 8/19/13 11:39 PM, Timon Gehr wrote:
>> On 08/20/2013 02:18 AM, Andrei Alexandrescu wrote:
>>>
>>> Why would it be necessary to return an object of type TypeTuple (i.e.
>>> template tuple)?
>>
>>
>> - Elegance. Eg:
>>
>> auto seq(T...)(T arg){ return arg; }
>>
>> auto fold(alias a,S,R)(S start, R range){ ... }
>>
>>
>> seq(0,[1,2,3]).fold!((a,b)=>a+b);
>
> But this is again a value tuple not a template tuple, no?
>
> Andrei

A value tuple IS a tuple. What we call a tuple here as nothing to do with a tuple.
August 21, 2013
On Tuesday, August 20, 2013 23:25:03 Michel Fortin wrote:
> On 2013-08-21 00:38:30 +0000, Andrei Alexandrescu
> 
> <SeeWebsiteForEmail@erdani.org> said:
> > 1. What do we need?
> 
> I think D needs less tuples. It has two kinds (language kind and Phobos's kind), which is confusing. It should have just one that covers most use cases.

I honestly think that trying to combine Tuple and TypeTuple would increase the confusion, because you'd be forcing the compiler to decide what you meant in any given situation, and from the programmer's perspective, it will frequently be non-obvious whether what's being done is being done at compile time or at runtime.

The main problem I see is that TypeTuple is badly named. Heck, the fact that it's even referred to as a tuple is problematic, since it isn't really, as it always expands. And the fact that it's the "built-in" tuple but requires a library solution to be able to actually declare it is a bit odd. So, I think that creating as syntax for the built-in tuples in order to get rid of TypeTuple would clean things up.

But I see no reason to try and combine any of that with Tuple, as Tuple and TypeTuple do fundamentally different things. The main gain I see is simply in cleaning up the naming mess, and by making it so that the built-in tuple actually has a built-in syntax, the language is cleaner and should be easier to understand (especially if we can come up with a name other than tuple or built-in tuple to call them so that they stop getting confused with Tuple).

- Jonathan M Davis
August 21, 2013
On Wednesday, 21 August 2013 at 00:38:31 UTC, Andrei Alexandrescu wrote:
> 1. What do we need?

Nothing. Everything discussed thus far can already be done in D, but with either a little or a lot of tedium and error-prone hackery. What I think would benefit D is a cleanup/simplification/sugarization of the current tuple situation. TypeTuple causes no end of grief and confusion.

> 2. What does a solution look like in the current language?

See Kenji's DIP for Bearophile's example of tuple use in the current language. Throughout this discussion there have also been several other examples of how TypeTuple misbehaves.

> 3. Which parts of the solution are frequent/cumbersome enough to warrant a language change?

IMO, TypeTuple auto-expansion and mixing of data/types within the same TypeTuple. It's also annoying that TypeTuples are not first-class values, and it would be a pity if we did not come up with some sort of tuple literal syntax, but that situation can be worked around by wrapping it in Tuple.

> Instead there have been 1001 proposals for new syntax for tuple literals, one cuter than the next.

Wouldn't you call this characterization a little unfair, when there were numerous posts also discussing relevant issues and semantics? I'd say the ratio of form:function has been pretty good thus far.

August 21, 2013
On Wednesday, 21 August 2013 at 00:38:31 UTC, Andrei Alexandrescu wrote:
> On 8/20/13 5:28 PM, Tyler Jameson Little wrote:
> Instead there have been 1001 proposals for new syntax for tuple literals, one cuter than the next.

I agree. However, that syntax issue is the bikeshed and it keeps everyone's mind busy. So, I propose to pick-up a syntax, even a provisional one, let's say that &(a,b) that I kinda like, then stick with it.

This will free the way for more important and fundamental issues and, on the way, it will also allow to discover the eventual shortcomings of the syntax that was picked-up.
August 21, 2013
On Wed, Aug 21, 2013 at 03:43:45AM +0200, bearophile wrote:
> Andrei Alexandrescu:
> 
> >1. What do we need?
> 
> We can live fine without a syntax to manage tuples, so strictly speaking we need nothing (almost, I'd like to kill one currently accepted syntax, see below).
> 
> But when you write D in a high-level style, and you are used to other functional or scripting languages, in several cases you desire a more handy/compact syntax to manage tuples.
> 
> Tuples are simple data structure, so the operations you want to do
> with them are few and simple:
> - To define a tuple of various fields of different type, that is a
> tuple literal, to create a tuple;

This is adequately taken care of by std.typecons.Tuple, isn't it?


> - A way to read and write items of a tuple (unless it's read-only), D uses a nice syntax [i] as arrays indexing, but i can't be a run-time value.

You can't make 'i' a runtime value because D is a statically-typed language. The compiler has to know at compile-time what type tup[i] is. Otherwise it couldn't generate machine code for things like:

	void func(Tuple t, int i) {
		auto x = t[i];	// what's the type of x?
	}


> - With the Phobos tuple we are used to giving names to fields. It's handy, but it's not so necessary if you have a way to assign tuple items to variables, defined in-place. There are several places where pulling apart a tuple in that way is useful, here I use a basic syntax to be more clear:
> 
> Assignments:
> auto (a, b) = t1;

Currently you could do:

	Tuple!(...) t1;
	auto a = t1[0];
	auto b = t1[1];

Not as nice, certainly, but I wouldn't consider it a deal-breaker.


> In function signatures:
> auto mult = ((int x, int y)) => x * y;

Currently we have:

	auto mult = (Tuple!(int,int) t) => t[0] * t[1];

Not as pretty, but surely still readable and usable? Or am I missing something?


> In foreach loops:
> 
> void main() {
>   import std.range, std.stdio;
>   auto a = [10, 20];
>   auto b = [100, 200];
> 
>   // Currently D supports this syntax:
>   foreach (x, y; zip(a, b))
>     writeln(x, " ", y);
> 
>   // But it's unreliable, now x is the index of the
>   // array instead of the first tuple fields, so I
>   // think this feature of D should be killed as
>   // soon as possible:
>   foreach (x, y; zip(a, b).array)
>     writeln(x, " ", y);
> }

Isn't this an auto-expanding tuple that Andrei didn't like?

Though granted, the current way of expressing it is not as nice:

	foreach (t; zip(a,b)) {
		writeln(t[0], " ", t[1]);
	}


[...]
> There are few more little pieces that are useful, like unpacking an array:
> 
> string s = "red blue";
> auto (col1, col2) = s.split;

What should happen if s is a runtime variable that may not have the same number of words as the number of elements in the tuple on the left-hand side? A runtime error?


[...]
> For an use case of the assignment from an array, let's say you have a text file containing two numbers in a line, that you want to read:
> 
> const (x, y) = filein.byLine.front.split.to!(int[]);
[...]

Again, what should happen if at runtime the lines have more or less elements than the tuple on the left-hand side? A runtime error?

I didn't look in detail at your red-black tree example, or the Huffman encoding example, but it would seem that currently, std.typecons.Tuple more-or-less suffices for your needs, right? Except for some syntactic unpleasantness, that is.

Or is there something that *can't* be expressed in an adequate way by the current Tuple that I missed?

Also, I note that none of your examples need TypeTuples at all, so it would appear that your use case do not necessitate the unification of TypeTuple with Tuple. Though that would be nice conceptually, it does introduce a lot of complications into the language and require addressing some non-trivial issues which, taking a step back, perhaps we don't *need* to address, because they are of little or no practical use?


T

-- 
Knowledge is that area of ignorance that we arrange and classify. -- Ambrose Bierce
August 21, 2013
On Monday, 19 August 2013 at 20:46:02 UTC, Andrei Alexandrescu wrote:
>> void main() {
>>     auto t1 = #(5, "hello", 1.5);
>>     auto (?,  ?, x) = t1;
>>     auto (?, gr, ?) = t1;
>> }
>>
>> Bye,
>> bearophile
>
> It's stuff like this that's just useless and gives a bad direction to the whole discussion. There's hardly anything wrong with auto x = t1[2] or auto gr = t1[1], but once the bikeshed is up for painting, the rainbow won't suffice.
>
>
> Andrei

I agree. The negative space *around* the bikeshed should be used...

auto (void, void, x) = t1;

(Just an idea I had. Don't know whether it's technically sound. It just seemed so funny to me that the idea popped into my head as soon as you said "rainbow". Not trying to waste time. )