September 23, 2012
This is a complex topic, and in this post I am not able to discuss everything that needs to be discussed. So I will discuss only part of the story.

First: tuples are important enough. I think they should be built-in in a modern language, but maybe having them as half-built-in will be enough in D. Currently in D we have (deprecated) built-in complex numbers that I use only once in a while, and half-usable library defined tuples that I use all the time.

Second: removing comma operator from D has some advantages unrelated to tuple syntax. Even disallowing bad looking C-like code that uses commas is an improvement by itself (but maybe it's not a big enough improvement...).

Third: replacing the packing syntax tuple(x,y) with (x,y) is nice and maybe even expected in a partially functional language as D, but that's _not_ going to improve D usability a lot. What I am asking for is different: I'd like D tuples to support handy unpacking syntax:

1) In function signatures;
2) In foreach;
3) At assignment points;
4) In switch cases.

Note: in the examples below I have used the tuple(x,y) syntax for simplicity, feel free to replace it with a shorter (x,y) syntax if you want.

---------------------------

1) This tuple unpacking use case is important, and it's not what you argue against in the DIP:

int f(tuple(int x, int y)) { return x + y; }
auto pairs = zip([10, 20], [2, 3]);
map!f(pairs)

This is different from what you are arguing against because this "f" is not accepting two normal ints, it accepts a tuple made of two ints, and names them "x" and "y" on the fly.

---------------------------

2)
auto pairs = zip([10, 20], [2, 3]).array();
foreach (i, tuple(x, y); pairs) {}

---------------------------

3) This is probably the most common use case:

auto foo() { return tuple(10, 20); }
auto tuple(x, y) = foo(); // bad syntax
auto (x, y) = foo(); // better syntax
void main() {
    auto a = tuple(1, 2);
    auto tuple(x1, x2) = a; // bad syntax
    auto (y1, y2) = a; // better syntax
}

---------------------------

4) This looks simple, but allows things like using BigInt in switch cases, implementing a very simple but quite handy pattern-matching, etc:

auto v = tuple(10, 20);
final switch (v) {
    case tuple(5, y): { x in scope... } break; // y is not a global
    case tuple(x, y): { ... } break; // this covers all cases
}

---------------------------

There are other usage patterns that are handy, but in my opinion the four I have shown here are the most commonly useful ones. See also Bugzilla issues 6365 and 6367, they shows some other cases and ideas and problems.

---------------------------

Tuple singletons: using (1,) as in Python is acceptable. using (1) is not acceptable in my opinion, too much dangerous. tuple(1) is also acceptable, it's longer, but it's not commonly used, so it's OK.

Empty tuples: the (,) syntax proposed in the DIP is not nice, it seems to have one invisible item, but maybe it's acceptable. tuple() is also acceptable, it's longer, but it's not commonly used, so it's OK.

In the end tuples with 0 and 1 items are sometimes useful, but they are not nearly as useful as supporting well tuples with 2 or more items. Scala language agrees with this.

---------------------------

Tuple slicing: it's not a fundamental operation, but it's nice to try to make it work correctly :-) I think not even Haskell does this well.

---------------------------

Summary:
- I think (1,2) is nicer and better than tuple(1,2), and I'd like to have such syntax, but it's not a large improvement and it's not what I am asking for now (less priority).
- Supporting tuples with 0 and 1 items is sometimes handy but I think it's not essential.
- On the other hand syntax to unpack/destructure tuples in many situations is important for allowing a proper and handy use of tuples in D.

Bye,
bearophile
September 23, 2012
On Sunday, 23 September 2012 at 21:37:06 UTC, Adam D. Ruppe wrote:
> I'm not for removing the comma operator, but it occurs to me we could do it in the library:
>
> auto commaOperatorReplacement(T...)(T t) {
>    return t[$-1];
> }
>
> There might be some edge case where that wouldn't work, but I think it works in most cases.

If D is like C in this regard, then the function above cannot replace comma operator, because the order of evaluation is defined for comma operator, but not for function parameters. You could use something like that, though:
September 23, 2012
On Sunday, 23 September 2012 at 22:29:31 UTC, jerro wrote:
> On Sunday, 23 September 2012 at 21:37:06 UTC, Adam D. Ruppe wrote:
>> I'm not for removing the comma operator, but it occurs to me we could do it in the library:
>>
>> auto commaOperatorReplacement(T...)(T t) {
>>   return t[$-1];
>> }
>>
>> There might be some edge case where that wouldn't work, but I think it works in most cases.
>
> If D is like C in this regard, then the function above cannot replace comma operator, because the order of evaluation is defined for comma operator, but not for function parameters. You could use something like that, though:

Sorry about the "You could use something like that, though" part. I realized "something like that" wouldn't work either, but forgot to delete that text.
September 23, 2012
On Monday, September 24, 2012 00:30:27 jerro wrote:
> If D is like C in this regard, then the function above cannot replace comma operator, because the order of evaluation is defined for comma operator, but not for function parameters.

I believe that it's currently undefined for D, but Walter wants to define it so that it's left-to-right in an effort to eliminate bugs resulting from the varying order of function argument evaluation. He just hasn't gotten around to doing it yet.

- Jonathan M Davis
September 23, 2012
On 09/23/2012 10:40 PM, Andrei Alexandrescu wrote:
> I discussed this with Walter, and we concluded that we could deprecate
> the comma operator if it helps tuples. So I started with this:
>
> http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP19
>
> Unfortunately, I started much cockier than I ended.  The analysis in
> there fails to construct a case even half strong that deprecating the
> comma operator could significantly help tuples.

That is because it does not base the discussion on the right
limitations of built-in tuples:

auto (a,b) = (1,"3");
(auto a, string b) = (1, "3");

BTW: the following works

Tuple!(int, string) t2 = t1[0 .. 2];

because of this:

=> (alias this)

Tuple!(int, string) t2; t2._fields = t1[0 .. 2];

=> (tuple assignment)

Tuple!(int, string) t2; t2._fields[0]=t1[0]; t2._fields[1]=t1[1];

> Well it essentially
> concludes that tuples are mostly fine as they are, and attempts to
> embellish them syntactically are marred with unexpected problems.
> Nevertheless, I sure have missed aspects all over, so contributions are
> appreciated.
>

- We already use the name 'tuple'. I'd suggest renaming that to
  'sequence' or similar. template Seq(T...){ alias T Seq; }

- The empty tuple can well be (), just like 'Seq!()' works without
  issues (it is an expression that is also a type). What is wrong with
  it?

- How do we expand a sequence into a tuple?
  => (Seq!(1,2,3),)

- What is the calling convention used for passing built-in tuples to
  and from functions?

- As tuples are built-in, expansion can be shorter than '.expand'.
  foo(1, tup..., 3); ?

- Template tuple parameters? This would work, but...
  template Tuple((T...,)){ alias (T,) Tuple; }

  void bar(T,(U...,),V...)(T delegate(U) dg, V args){ ... }
  void foo(T,(U...,),(V...,))(T delegate(U) dg, V args){
     bar!(T,Tuple!U,V)(dg, args);
  } // U and V can be passed separately

- Named tuple fields?

  (int x, int y) tuple = (1,2);

  swap(tuple.x, tuple.y);

September 23, 2012
On 9/23/12 5:57 PM, deadalnix wrote:
> Le 23/09/2012 23:52, Nick Sabalausky a écrit :
>> Ok, here's a crazy idea:
>>
>> Do the reasons for explicit tuple-expansion necessarily apply to
>> zero- and one-element tuples? I'm not so sure. Suppose we allowed
>> implicit expansion on those...
>>
>> Now I know what you're thinking: That would be an ugly inconsistency
>> between tuples of sizes>1 vs<=1. Well, *mechanically* yes, but
>> consider this:
>>
>> *Logically* speaking, is there really any difference between a
>> one-element tuple and an ordinary single value? I don't think so, and
>> here's why: What is a tuple, logically speaking? Multiple values being
>> handled as if they were a single value. So what's a one-element tuple?
>> *One* value being handled as if it were one value - which is *is*.
>>
>> Similarly, a zero-element tuple is logically equivalent to void (or the
>> one value a void can have: the value void, a concept which has been
>> argued in the past that might be useful for D, particularly in
>> metaprogramming). (I admit this is a little weaker than my argument
>> for one-element tuples.)
>>
>> So perhaps zero- and one-element tuples should be implicitly
>> convertible back and forth with void and ordinary non-tuple values,
>> respectively (polysemous values?), because that's what they essentially
>> are.
>>
>
> My reflection on the subject for the past month lead me to the same
> conclusion.

This notion a lot of trouble with it; I think it's safe to abandon it entirely.

Once a one-element tuple becomes equivalent to the actual item, there's an explosion of trouble and special cases in the language and in code that uses it. For example, divide and conquer code that manipulates tuples and takes t[0 .. $/2] and t[$/2+1 .. $] would suddenly get to cases in which the slices are no longer tuples, and so on. And that's only the beginning.

Also, having no integrated notion of a zero-element tuple would again mess with the algebra as much as the absence of 0 would hurt numbers. It's just troublesome.

I appreciate the attraction of this idea, but again I think it's safe to just not even discuss it.


Andrei
September 23, 2012
On 09/24/2012 12:11 AM, bearophile wrote:
> ...
>
> Second: removing comma operator from D has some advantages unrelated to
> tuple syntax. Even disallowing bad looking C-like code that uses commas
> is an improvement by itself (but maybe it's not a big enough
> improvement...).

I would think that it isn't an improvement at all. Disallowing some
construct always will also disallow 'bad looking' code that uses it.

>...
>
> 4) This looks simple, but allows things like using BigInt in switch
> cases, implementing a very simple but quite handy pattern-matching, etc:
>
> auto v = tuple(10, 20);
> final switch (v) {
>      case tuple(5, y): { x in scope... } break; // y is not a global
>      case tuple(x, y): { ... } break; // this covers all cases
> }
>
> ...

cases already introduce their own scopes in D, but switch cannot be
extended well to serve such use cases.

I agree with all the other points.
September 23, 2012
On 09/24/2012 12:40 AM, Jonathan M Davis wrote:
> On Monday, September 24, 2012 00:30:27 jerro wrote:
>> If D is like C in this regard, then the function above cannot
>> replace comma operator, because the order of evaluation is
>> defined for comma operator, but not for function parameters.
>
> I believe that it's currently undefined for D, but Walter wants to define it so
> that it's left-to-right in an effort to eliminate bugs resulting from the
> varying order of function argument evaluation. He just hasn't gotten around to
> doing it yet.
>
> - Jonathan M Davis
>

I believe it is currently left-to-right for D, in all kinds of
expressions, but DMD does not implement it yet.
September 23, 2012
On 9/23/12 6:42 PM, Timon Gehr wrote:
> That is because it does not base the discussion on the right
> limitations of built-in tuples:
>
> auto (a,b) = (1,"3");
> (auto a, string b) = (1, "3");

I meant to mention that but forgot. The interesting thing about this is that, if we decide it's the main issue with today's tuples, we pull Kenji's patch and close the case.

> BTW: the following works
>
> Tuple!(int, string) t2 = t1[0 .. 2];
>
> because of this:
>
> => (alias this)
>
> Tuple!(int, string) t2; t2._fields = t1[0 .. 2];
>
> => (tuple assignment)
>
> Tuple!(int, string) t2; t2._fields[0]=t1[0]; t2._fields[1]=t1[1];

Yah, I thought the writeup clarified that.

> - We already use the name 'tuple'. I'd suggest renaming that to
> 'sequence' or similar. template Seq(T...){ alias T Seq; }

Then what are the "old" tuples?

> - The empty tuple can well be (), just like 'Seq!()' works without
> issues (it is an expression that is also a type). What is wrong with
> it?

There's already intensive use of parens in D. I predict there's going to be big trouble with "()" even assuming it's not technical ambiguous, for example a lambda that returns an empty tuple would be "()() {...}" and all that jazz.

> - How do we expand a sequence into a tuple?
> => (Seq!(1,2,3),)

I think we're discussing different things - the above seems to deal with expression/alias tuples. DIP19 discusses strictly runtime value tuples.

> - What is the calling convention used for passing built-in tuples to
> and from functions?

I don't know. The current approach with .expand is nothing special - as if the programmer wrote the expansion by hand.

> - As tuples are built-in, expansion can be shorter than '.expand'.
> foo(1, tup..., 3); ?

I find that sugar gratuitous.

> - Template tuple parameters? This would work, but...
> template Tuple((T...,)){ alias (T,) Tuple; }
>
> void bar(T,(U...,),V...)(T delegate(U) dg, V args){ ... }
> void foo(T,(U...,),(V...,))(T delegate(U) dg, V args){
> bar!(T,Tuple!U,V)(dg, args);
> } // U and V can be passed separately
>
> - Named tuple fields?
>
> (int x, int y) tuple = (1,2);
>
> swap(tuple.x, tuple.y);

I kinda got lost around all that.


Andrei
September 23, 2012
Le 24/09/2012 00:48, Andrei Alexandrescu a écrit :
> This notion a lot of trouble with it; I think it's safe to abandon it
> entirely.
>
> Once a one-element tuple becomes equivalent to the actual item, there's
> an explosion of trouble and special cases in the language and in code
> that uses it. For example, divide and conquer code that manipulates
> tuples and takes t[0 .. $/2] and t[$/2+1 .. $] would suddenly get to
> cases in which the slices are no longer tuples, and so on. And that's
> only the beginning.
>

This is a very weak point. In most cases, divide an conquer with tuple don't even make sense.

> Also, having no integrated notion of a zero-element tuple would again
> mess with the algebra as much as the absence of 0 would hurt numbers.
> It's just troublesome.
>
> I appreciate the attraction of this idea, but again I think it's safe to
> just not even discuss it.
>

I'm not sure I want to answer that, so I wont.