Thoughts on possible tuple semantics

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Aug 22 12:47:05 PDT 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.


More information about the Digitalmars-d mailing list