Thread overview
Re: Tuple literal syntax
Oct 08, 2010
bearophile
Oct 09, 2010
so
Oct 09, 2010
Justin Johansson
Oct 09, 2010
bearophile
Oct 09, 2010
so
Oct 09, 2010
Simen kjaeraas
Oct 09, 2010
so
October 08, 2010
Lars T. Kyllingstad:

>I know, and I agree that *if* it is decided that tuples should be added to the language, it has to happen now.  I just don't think it's necessary to do it at all.<

A better built-in support for tuples is a form of syntax sugar, this means it doesn't give you more power, it doesn't give you ways to do things you were unable to do before. So it's not necessary. On the other hand sometimes it's good to have some syntax sugar, because when it's well chosen it may allow you to write less bug-prone code, to write more readable code, to write less code, and sometimes even to write more efficient code, etc.

Some syntax to support tuples better is (mostly) an additive change, this means that tuple syntax may be added to D3 instead of D2. There is no need to add it now. But it's a basic language feature, so it's better to add it soon, and allow people that write Phobos to use and enjoy it sooner.

As with many other language features, it's not easy to understand how much useful (or how much useless) a feature is if you have never used it before for some time. For example I have heard about Design by Contract before trying it in D, but I have never used it for real before trying it for months in D and discussing about it for months in this newsgroup. Now I am sold and I use a poor's man DbC even in my Python code, and I think it's very useful, and I've asked for this PEP to be implemented when Python will come out of its current design hiatus (http://www.python.org/dev/peps/pep-0316/ ). This means that if you have not used tuples much in other languages, then it will be hard for you to understand why they are useful and good to have. Sometimes it happens that a feature X that's very useful in language Y is not so useful in language Z, so you need care to avoid adding it to Z.

Currently std.typecons.tuple is useful for several different purposes, but in my opinion, on the base of tuple usage I've had in Python (and cons cells in Scheme-like languages), I think its usage is hindered by a lack of a more clean syntax. So I think some syntax sugar may free more potential usages of tuples in D. I presume Walter and Andrei agree on this.

Experience has shown me that very often there is a "weight" threshold: if the syntax and semantics needed to use a feature is simple enough and short enough, then many programmers will use it, otherwise it will not be used much even if it's useful. The syntax sugar for tuples is needed to lower enough the "potential barrier" to access tuples to make their usage convenient in many situations.

In a single post I can't show you what's good in tuple usage in Python, you need practical usage experience for that. But I can show some of the tuple syntax that may be needed to use tuples at their best, showing what's bad/hard to do now, and how it may become handy/better.

We have to keep in mind that tuple usage also has some disadvantages, so there are situations where using tuples makes the program worse. The usage of tuples with unnamed fields as function return values may reduce code readability a bit, making it less explicit. A heavy usage of anonymous tuples is like a heavy usage of anonymous functions, you lose some names, and this worsens your stack traces (where you see labels just like lambda1, lambda2, lambda3, etc instead of semantically meaningful function names), it may worsen your debugging and code readability, etc. Sometimes a language that's easy to write is less easy to read, for example Python dynamic typing allows you to write code faster, but much later the lack of types in function signatures may make it less easy to understand the meaning of the code. If you want to write code that's meant to be debugged and used for many years, then using lots of anonymous tuples may be negative. As most tools, tuples may be abused, so you need to use them with care only where they improve the code or your work.


There are two kinds of syntax sugar that may be useful for tuples: syntax to build/define a tuple and syntax to de-structure/match it.

A tuple is useful to group and keep related data together, like a struct. The tuple offers a lighter and shorter way to define it, and better ways to de-structure the data later. Tuples are quite useful to allow functions to return multiple values. Returning a single values in C/C++/Java is a silly limitation, in many situations you need to return more than a value. You may use "out" arguments, but they are a HACK, they are semantically unclean, because you may assign a out value outside a function before its call (and this value gets silently ignored and overwritten) and the syntax is unclean, because in mathematics function arguments are things that go inside the function.

Usage of languages like Python (and Go) shows you that it's very handy and clean to be able to return two or more values from functions (this means a 2-tuple or 3-tuple). If you return too many items you may lose track of what the function returns, so it's good to limit the number of return values to just few, usually just 1, 2 or 3.


In Python there is a built-in function named divmod, given two numbers the quotient and remainder of their integer division. If you want you may redefine (and use) in a naive way it again like this (in Python it's useful if x and y are large multi-precision integers, it reduces a lot the total amount of computations done if you need both results):


>>> def divmod(x, y):
...     return x // y, x % y
...
>>> d, r = divmod(14, 4)
>>> print d
3
>>> print r
2



This is a possible way to do the same thing in D2:


import std.stdio: writeln;
import std.bigint: BigInt;

// std.bigints ridicolously lack a way to print them
const(char)[] repr(BigInt i) {
    const(char)[] result;
    i.toString((const(char)[] s){ result = s; }, "d");
    return result;
}

void divmod(BigInt x, BigInt y, out BigInt q, out BigInt r) {
    q = x / y;
    r = x % y;
}

void main() {
    BigInt q, r;
    // never initialize q and r here
    divmod(BigInt(14), BigInt(4), q, r);
    writeln(repr(q));
    writeln(repr(r));
}


The divmod1() may also return q and use r as out value, but this breaks symmetry in the q and r return values and I don't like it much.

You may use std.typecons.tuple:


import std.stdio: writeln;
import std.bigint: BigInt;
import std.typecons: tuple, Tuple;

const(char)[] repr(BigInt i) {
    const(char)[] result;
    i.toString((const(char)[] s){ result = s; }, "d");
    return result;
}

Tuple!(BigInt, "q", BigInt, "r") divmod(BigInt x, BigInt y) {
    return typeof(return)(x / y, x % y);
}

void main() {
    Tuple!(BigInt, "q", BigInt, "r") q_r = divmod(BigInt(14), BigInt(4));
    writeln(repr(q_r.q));
    writeln(repr(q_r.r));
}


You may also use auto and anonymous fields to shorten the code a little:


import std.stdio: writeln;
import std.bigint: BigInt;
import std.typecons: tuple;

const(char)[] repr(BigInt i) {
    const(char)[] result;
    i.toString((const(char)[] s){ result = s; }, "d");
    return result;
}

auto divmod(BigInt x, BigInt y) {
    return tuple(x / y, x % y);
}

void main() {
    auto q_r = divmod(BigInt(14), BigInt(4));
    writeln(repr(q_r[0]));
    writeln(repr(q_r[1]));
}


"auto" reduces the amount of code, but a heavy usage of type inference may make it harder to understand what types your code is using months later when you read your code again.

There are few different ways to design the syntax sugar to build and de-structure the tuple used in that program. A possible syntax to build a tuple is just to use tuple() or record(). This is not bad, and it's very readable, but it's a bit long, so if you use tuples in complex expressions this may make your expression too much long. To shorten the syntax a little you may use what Andrei has named banana syntax (using just () to denote tuples is more handy, but it introduces a corner case for 1-tuples and it's not backward compatible with C syntax):


auto divmod1(BigInt x, BigInt y) {
    return (| x / y, x % y |);
}


Or a syntax that just avoids to use strings to represent field values:

Tuple!(BigInt q, BigInt r) divmod(BigInt x, BigInt y) {
    return typeof(return)(x / y, x % y);
}


Some possible syntaxes for the unpacking, others may be possible:

void main() {
    auto record(q, r) = divmod1(BigInt(14), BigInt(4));
    writeln(repr(q));
    writeln(repr(q));
}

void main() {
    (|BigInt q, BigInt r|) = divmod1(BigInt(14), BigInt(4));
    writeln(repr(q));
    writeln(repr(q));
}

void main() {
    (|auto q, auto r|) = divmod1(BigInt(14), BigInt(4));
    writeln(repr(q));
    writeln(repr(q));
}

void main() {
    auto (|q, r|) = divmod1(BigInt(14), BigInt(4));
    writeln(repr(q));
    writeln(repr(q));
}


(the versions with and without auto may be allowed at the same time.)


This is a function of std.file (Phobos2 of DMD 2.047):
void getTimes(in char[] name, out d_time ftc, out d_time fta, out d_time ftm);

Introducing tuples more into Phobos its signature becomes:
Tuple!(d_time "ftc", d_time "fta", d_time "ftm") getTimes(const string name);
Or:
Record!(d_time "ftc", d_time "fta", d_time "ftm") getTimes(const string name);

With some syntax sugar:
Record!(d_time ftc, d_time fta, d_time ftm) getTimes(const string name);
Or:
(|d_time ftc, d_time fta, d_time ftm|) getTimes(const string name);


Once you have added the unpackign syntax you may call that function as:

void main() {
    // Here I have used "fc" != "ftc"
    (|d_time fc, d_time fa, d_time fm|) = getTimes("filename");
}

Or just like this, that is short and sometimes good enough:

void main() {
    auto (|fc, fa, fm|) = getTimes("filename");
}


That's syntax sugar for something like:

void main() {
    auto __temp1 = getTimes("filename");
    d_time fc = __temp1.field[0];
    d_time fa = __temp1.field[1];
    d_time fm = __temp1.field[2];
}



In Python2 nested unpacking too is available, and it may be supported by D too:

(|int x, (|int y, int z|)|) = foo();


An unpacking syntax for Tuples is useful to replace the very similar zip() and
lockstep():


import std.algorithm, std.stdio, std.range;
void main() {
    foreach (p; zip([1, 2, 3], "abcd"))
        writeln(p._0, " ", p.length, " ", p._1);
    writeln();
    foreach (i, a, b; lockstep([1, 2, 3], "abcd"))
        writeln(i, " ", a, " ", b);
}


with a single zip() that may be used for both situations:

import std.algorithm, std.stdio, std.range;
void main() {
    foreach (p; zip([1, 2, 3], "abcd"))
        writeln(p[0], " ", p[1]);
    writeln();
    foreach ((a, b); zip([1, 2, 3], "abcd"))
        writeln(a, " ", b);
}


As in Python:

for p in zip([1, 2, 3], "abcd"):
    print p[0], p[1]
print
for (a, b) in zip([1, 2, 3], "abcd"):
    print a, b


(The zip() is a very commonly useful higher order function.)


A related handy feature, present in Python2 is de-structuring (unpacking) in function signature (this is a small amount of pattern matching that's missing in Python3):

>>> def foo((x, y), z): print y
...
>>> foo("ab", 2)
b


Walter has also suggested a syntax that is often useful in functional programming, to de-structure the head and tail (car and crd) of sequences (something similar is present in Python3 and absent in Python2):

auto (|car, cdr...|) = expr;

This is not a complete introduction to tuple usefulness, but it gives you some ideas. If you have questions, feel free to ask.

Bye,
bearophile
October 09, 2010
> ...
> auto (|car, cdr...|) = expr;
>
> This is not a complete introduction to tuple usefulness, but it gives you some ideas. If you have questions, feel free to ask.
>
> Bye,
> bearophile

The syntax is too ugly, i don't know how you call it "sugar" :)
Sorry for the one liner. (Though it is not a one liner anymore!)

-- 
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
October 09, 2010
On 9/10/2010 7:29 PM, so wrote:
>
> The syntax is too ugly, i don't know how you call it "sugar" :)

Perhaps "saccharin" then?  :-)

October 09, 2010
so:

> The syntax is too ugly, i don't know how you call it "sugar" :)

If you take a look at Fortress you may see how much they use that kind of syntax. And they are able to convert the ASCII code into more elegant pages that use special chars. It's a clean syntax, I think it has no corner cases, I think it doesn't collide with C comma syntax operator, and it's shorter than the range()/tuple() syntax. So I don't agree with you :-)

Bye,
bearophile
October 09, 2010
This must have been answered many times already but i shamelessly ask once again..
What is the problem with :

[a, b, [c, d], e...]

At first glance it doesn't have the cases braces have and it looks much better.

Thanks!

On Sat, 09 Oct 2010 16:08:17 +0300, bearophile <bearophileHUGS@lycos.com> wrote:

> so:
>
>> The syntax is too ugly, i don't know how you call it "sugar" :)
>
> If you take a look at Fortress you may see how much they use that kind of syntax. And they are able to convert the ASCII code into more elegant pages that use special chars. It's a clean syntax, I think it has no corner cases, I think it doesn't collide with C comma syntax operator, and it's shorter than the range()/tuple() syntax. So I don't agree with you :-)
>
> Bye,
> bearophile


-- 
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
October 09, 2010
so <so@so.do> wrote:

> This must have been answered many times already but i shamelessly ask once again..
> What is the problem with :
>
> [a, b, [c, d], e...]
>
> At first glance it doesn't have the cases braces have and it looks much better.

Uhm, it's an array? That is, the syntax is already taken and means
something else.


-- 
Simen
October 09, 2010
Hahaa! Thanks for quick answer. I sometimes forget this is D, not C.

On Sat, 09 Oct 2010 21:27:29 +0300, Simen kjaeraas <simen.kjaras@gmail.com> wrote:

> so <so@so.do> wrote:
>
>> This must have been answered many times already but i shamelessly ask once again..
>> What is the problem with :
>>
>> [a, b, [c, d], e...]
>>
>> At first glance it doesn't have the cases braces have and it looks much better.
>
> Uhm, it's an array? That is, the syntax is already taken and means
> something else.
>
>


-- 
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/