August 20, 2013
On 8/20/13 1:24 PM, Dicebot wrote:
>> 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.)
>
> :O it is awesome!

"Spawn of Satan" would be a tad more appropriate.

> Stuff like foo(myStructInstance.tupleof) is very
> powerful tool for generic interfaces.

One problem with automatic expansion is that now there's a whole new kind - a single expression that doesn't quite have one value and one type, but instead is an explosion of other expressions.

The problem now is how to contain these things - presumably one should associate a self-exploding tuple with a symbol:

auto pack = functionReturningTuple();

The symbol should not expand when, for example, assigned to another:

auto packCopy = pack;

However, the symbol _should_ expand "where it should", presumably in a function code:

auto m = min(pack, 0);

So this will invoke min with various numbers of arguments depending on what pack contains. Same with e.g.

writeln(pack);

Before one even realizes it, the matter of when to expand vs. when not to expand becomes a thing. That thing will have people confused (because now an expression isn't what it seems; it may be, in fact, several expressions at once). It will get explained in articles and books. There will be debates about choices of automatic unpacking vs. not in which reasonable people may disagree. People will get all confused about it. Generic code won't be able to tell much about what's going on because arity can't be guaranteed anymore.

Alternatively, we could do what Go does and prevent all packing altogether (if I understand what Go does correctly). That is, if a function returns a tuple you can't even keep that tuple together unless you use some handcrafted solution. In that case, before long there will be some "Pack" structure and some pack helper function, viz. the converse of .expand.

Clearly both packing and unpacking tuples are necessary. The question is what the default is. My argument here is that keeping one expression/symbol have one value is so deeply embedded in the semantics (and the ethos for that matter) of D, that it would be a major step backwards to retrofit it all to accommodate self-expanding tuples. Not speaking for other languages, I think it's great that in D

writeln(x);

is known to take an argument, whereas

writeln(x.expand);

takes zero or more arguments. It is the best compromise I can imagine that keeps the language sealed tight.

> Do you remember any keywords to
> look for to find those discussions? Ones I remember were mostly about
> auto-expansion _not_ happening in some reasonable places.

Ionno so I just summarized it.


Andrei

August 20, 2013
On 08/20/2013 10:16 PM, Andrei Alexandrescu wrote:
> (There have been a few discussions in this group; I've come to think
> auto expansion is fail.)

+1. :)
August 20, 2013
On Tue, Aug 20, 2013 at 05:12:43AM +0200, Meta wrote:
> Aggh, misposted. Let's try that again.
> 
> On Tuesday, 20 August 2013 at 02:51:20 UTC, Meta wrote:
> >On Tuesday, 20 August 2013 at 01:06:28 UTC, H. S. Teoh wrote:
> >>Actually, reading through DIP32 again, it sounds like Kenji is proposing the *same* syntax for both built-in tuples and std.typecons.Tuple. In the code example under "Generic type/expression tuple syntax", he refers to them respectively as "tuple type" and "tuple value". Mixing is also allowed (e.g., in the "alias Fields" line).
> 
> Maybe mixing should be disallowed, then, unless your tuple was constructed from a variadic template argument list.
[...]

I don't like this "unless". Either we support mixing, or we don't. Adding in "unless" adds unnecessary complication, which will inevitably have a ripple effect that adds complications everywhere. Next thing you know, Phobos will acquire a template that returns a tuple of its arguments -- as a workaround for the inability to manually construct a mixed tuple, and then we'll have a rehash of this thread, this time surrounding how to merge/get rid of std.typecons.createMixedTuple.

I think any workable design of tuples must include a sane way of working with mixed tuples, because they can and do appear in template arguments.


T

-- 
Без труда не выловишь и рыбку из пруда.
August 20, 2013
On Tuesday, 20 August 2013 at 21:25:11 UTC, Andrei Alexandrescu wrote:
> "Spawn of Satan" would be a tad more appropriate.

I can not agree more.
August 20, 2013
On Tuesday, 20 August 2013 at 21:25:11 UTC, Andrei Alexandrescu wrote:
> Ionno so I just summarized it.
>
>
> Andrei

I guess  I need just deal with it and accept as given then :) Okay, it does not really change much for main issue I wanted to pay attention to - thin edge between run-time expression tuples and compile-time ones.

Only problematic moment I can see so far is that compile-time tuples can contain both types and expressions at the same time. But this can be solved by considering them non-instantiatable type tuples - similar to existing handling. Or just call them "template argument lists" to be 100% straight and reserve word "tuple" for expression tuple while preserving "typeof" relation (and enhancing expression tuples with ABI).

Sounds like dreaming? :)
August 21, 2013
On Tuesday, 20 August 2013 at 21:25:11 UTC, Andrei Alexandrescu wrote:
> On 8/20/13 1:24 PM, Dicebot wrote:
>>> 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.)
>>
>> :O it is awesome!
>
> "Spawn of Satan" would be a tad more appropriate.

+1

I can stand explicit expansion though, like Go's variadic argument ellipses syntax: http://golang.org/doc/effective_go.html#append

OT: I'm not really a fan of D's variadic function syntax. I'd prefer it to be explicit:

    int sum(int[] ar ...) {
    }
    int[3] foo = [4, 5, 6];
    sum(foo...);
    sum(3, 4, foo...); // on my wish list...

>> Stuff like foo(myStructInstance.tupleof) is very
>> powerful tool for generic interfaces.
>
> One problem with automatic expansion is that now there's a whole new kind - a single expression that doesn't quite have one value and one type, but instead is an explosion of other expressions.
>
> snip...
>
> Alternatively, we could do what Go does and prevent all packing altogether (if I understand what Go does correctly). That is, if a function returns a tuple you can't even keep that tuple together unless you use some handcrafted solution. In that case, before long there will be some "Pack" structure and some pack helper function, viz. the converse of .expand.

+1

I've been itching for multiple returns in D for a long time, and this seems like a nice way to add it in. I think I'd prefer to use tuple syntax instead though, just so there's less magic:

    (int, int) doStuff() {
        return (1, 2);
    }

    // syntax error
    auto a, b = doStuff();
    // ok
    auto (a, b) = doStuff();

> Clearly both packing and unpacking tuples are necessary.
>
> snip...
>
> Andrei

OT: is the only thing stopping us from using the nice (x,y) syntax for tuples the comma operator? If so, can we take that mis-feature behind the woodshed and shoot it? I sincerely hope nobody is relying on it...
August 21, 2013
On 8/20/13 5:28 PM, Tyler Jameson Little wrote:
> OT: is the only thing stopping us from using the nice (x,y) syntax for
> tuples the comma operator? If so, can we take that mis-feature behind
> the woodshed and shoot it? I sincerely hope nobody is relying on it...

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?

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


Andrei

August 21, 2013
2013/8/21 Timon Gehr <timon.gehr@gmx.ch>

> On 08/20/2013 10:16 PM, Andrei Alexandrescu wrote:
>
>> (There have been a few discussions in this group; I've come to think
>> auto expansion is fail.)
>>
>
> +1. :)
>

+1, too.

Since I wrote a compiler patch for the auto expansion on function arguments, but it was rejected by Andrei. http://d.puremagic.com/issues/show_bug.cgi?id=2779

And today, I'm mostly consent in his opinion.

Kenji Hara


August 21, 2013
2013/8/21 Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org>

> I think this whole thread is approaching things wrongly. It should be:
>
> 1. What do we need?
>

Both
a. Built-in tuple construction syntax
and
b. Built-in tuple deconstruction syntax

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

a.
  alias Types = {int, long};
  auto values = {1, "str"};
b.
  auto {a, b} = tup;  // on variable declaration
  {a, b} = tup;       // on assignment expression
  ...

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

a1. To make std.typetuple.TypeTuple template unnecessary.
a2. To keep std.typecons.Tuple as-is.
a3. To make expression tuple more usable.

  Today making the built-in tuple of runtime values needs unavoidable
runtime cost.

  int x, y;
  auto tup = TypeTuple!(x+1, y+2);
  // NG, template cannot take x+1 and y+2 because they are
  // expressions which cannot be evaluated in compile time
  auto tup = tuple(x+1, y+2);
  // NG, tup is not a builti-in tuple (it is an object of
std.typecons.Tuple struct).
  auto tup = tuple(x+1, y+2).expand;
  // OK, tup is a builti-in tuple. But we cannot avoid the cost to pack
in std.typecons.Tuple.

  Built-in tuple syntax would solve the issue.

  auto tup = {x+1, y+2};
  // tup is a built-in tuple, and it would be equivalent with:
  // auto tup[0] = x+1, tup[1] = x*2; // pseudo code
  // so no packing cost in runtime is there.

b.
  To make deconstruction code more readable.

  auto tup = std.typecons.tuple(1, "str");
  auto a = tup[0], b = tup[1];    // Currrent:
  // Deconstruct two fields in tup to the two variables a and b
  auto {a, b} = tup;    // New syntax:
  // Deconstruct syntax would recognize alias this tuple,
  // and expand it automatically.

Kenji Hara


August 21, 2013
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;
- 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.
- 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;

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

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);
}


Its output:

10 100
20 200
0 Tuple!(int, int)(10, 100)
1 Tuple!(int, int)(20, 200)


In (final) switch cases, this also shows one use case and one usefulness of wildcards (and the usage of an unapply() standard method, as explained a bit here: http://d.puremagic.com/issues/show_bug.cgi?id=596 ):

final switch (tup1) {
    case (0, 10): break;
    case (0,  ?): break;
    case (?,  ?): break;
}


An nice example usage of such basic pattern matching is visible here, to implement the insertion in a red-black-tree. This is a well known hairy operation to do in languages as C:
http://rosettacode.org/wiki/Pattern_matching


This is how insertion and balancing is done in Haskell (this needs long lines and it probably comes out unreadable here, so see here for a better visualization: http://rosettacode.org/wiki/Pattern_matching#Haskell ):

data Color = R | B
data Tree a = E | T Color (Tree a) a (Tree a)

balance :: Color -> Tree a -> a -> Tree a -> Tree a
balance B (T R (T R a x b) y c          ) z d                               =
    T R (T B a x b) y (T B c z d)
balance B (T R a           x (T R b y c)) z d                               =
    T R (T B a x b) y (T B c z d)
balance B a                               x (T R (T R b y c) z d          ) =
    T R (T B a x b) y (T B c z d)
balance B a                               x (T R b           y (T R c z d)) =
    T R (T B a x b) y (T B c z d)
balance col a x b = T col a x b

insert :: Ord a => a -> Tree a -> Tree a
insert x s = T B a y b where
  ins E          =  T R E x E
  ins s@(T col a y b)
    | x < y      =  balance col (ins a) y b
    | x > y      =  balance col a y (ins b)
    | otherwise  =  s
  T _ a y b = ins s



There are few more little pieces that are useful, like unpacking an array:

string s = "red blue";
auto (col1, col2) = s.split;


Hara has suggested pattern matching in if statements, that is a special case of the pattern matching in switch cases, and it's sometimes useful (similar code is common in Scala):

if (auto {1, y} = tup) {...}

That is equivalent to:

if (tup[0] == 1) {
    auto y = tup[1];
    ...
}

It looks simple and not so essential, but when tuples become a little more more complex pattern matching shows it usefulness better, as in the Haskell example.

Languages as Haskell and Rust also show that even a simple version of pattern matching is handy when you manage types like Nullable() (that need to define an unapply() standard method).


(It's not unthinkable to support pattern matching in function signatures too, as often used in many functional languages as OCaML and Haskell, but this is an orthogonal feature, it could be added later, and we can probably live without it for now).


Others have suggested a syntax with ... to gather more than one item, or to ignore more than one item with ?...  Such syntax is handy with arrays but with tuples, that are often short and of well known size I think it's not so much useful, so it can be left for later.


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

The DIP32 shows a complete very small program that I wrote for RosettaCode site, it computes the Huffman encoding of a given string:
http://wiki.dlang.org/DIP32


import std.stdio, std.algorithm, std.typecons, std.container, std.array;

auto encode(T)(Group!("a == b", T[]) sf) {
    auto heap = sf.map!(s => tuple(s[1], [tuple(s[0], "")]))
                .array.heapify!q{b < a};

    while (heap.length > 1) {
        auto lo = heap.front; heap.removeFront;
        auto hi = heap.front; heap.removeFront;
        foreach (ref pair; lo[1]) pair[1] = '0' ~ pair[1];
        foreach (ref pair; hi[1]) pair[1] = '1' ~ pair[1];
        heap.insert(tuple(lo[0] + hi[0], lo[1] ~ hi[1]));
    }
    return heap.front[1].schwartzSort!q{tuple(a[1].length, a[0])};
}

void main() {
    auto s = "this is an example for huffman encoding"d;
    foreach (p; s.dup.sort().release.group.encode)
        writefln("'%s'  %s", p[]);
}


That little program shows only three use cases of a tuple syntax: in function signatures, in normal assignments, and in foreach:

import std.stdio, std.algorithm, std.container, std.array;

auto encode(T)(Group!("a == b", T[]) sf) {
    auto heap = sf.map!((c, f) => (f, [(c, "")])).array.heapify!q{b < a};

    while (heap.length > 1) {
        auto (lof, loa) = heap.front;  heap.removeFront;
        auto (hif, hia) = heap.front;  heap.removeFront;
        foreach ((_, ref e); loa) e = '0' ~ e;
        foreach ((_, ref e); hia) e = '1' ~ e;
        heap.insert((lof + hif, loa ~ hia));
    }
    return heap.front[1].schwartzSort!((c, e) => (e.length, c));
}

void main() {
    auto s = "this is an example for huffman encoding"d;
    foreach ((c, e); s.dup.sort().release.group.encode)
        writefln("'%s'  %s", c, e);
}



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[]);

An example of implementation in a red-black-tree of the insert and balance in C is not too much hard to find with Google. But it's lot of bug-prone code.

Bye,
bearophile