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