Jump to page: 1 2
Thread overview
D with experimental features like tuple unpacking
Mar 21, 2013
bearophile
Mar 21, 2013
Rob T
Mar 22, 2013
Denis Shelomovskij
Mar 22, 2013
Dmitry Olshansky
Mar 22, 2013
bearophile
Mar 22, 2013
Dmitry Olshansky
Mar 22, 2013
renoX
Mar 22, 2013
Dmitry Olshansky
Mar 22, 2013
renoX
Mar 22, 2013
Tobias Pankrath
Mar 22, 2013
Tobias Pankrath
Mar 22, 2013
Dmitry Olshansky
Mar 26, 2013
bearophile
Mar 22, 2013
John Colvin
Mar 22, 2013
bearophile
Mar 22, 2013
ixid
Mar 22, 2013
Dmitry Olshansky
Mar 22, 2013
Jacob Carlborg
March 21, 2013
UFCS, plus some tweaks to the standard library like this one (http://d.puremagic.com/issues/show_bug.cgi?id=8755 ) help write D code in a more functional (flow programming, Walter calls it component programming) style.

So tuples become more and more useful, and the lack of a syntax to unpack them becomes worse. So with time this unpacking syntax has bubbled up to become about my top enhancement request.

One good way to show what I mean is to actually show some code without and with such hypothetical syntax. The code needs to work, to be short, and not to be contrived. This computes the Huffman encoding of a dchar[]:


// --------------------------------------
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!(p => tuple(p[1].length, p[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.tupleof);
}
// --------------------------------------


sort is followed by () because for unknown reasons we have not yet deprecated/removed the broken built-in array sort.


The same code with unpacking syntax:


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

auto encode(T)(Group!("a == b", T[]) sf) {
    auto heap = sf.map!(((c, f)) => tuple(f, [tuple(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 ((c, ref e); loa)
            e = '0' ~ e;
        foreach ((c, ref e); hia)
            e = '1' ~ e;
        heap.insert(tuple(lof + hif, loa ~ hia));
    }

    return heap.front[1].schwartzSort!(((c, e)) => tuple(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);
}
// --------------------------------------


The noisy [0] [1] are vanished from the encoding function.

In that code tuple unpacking happens in normal assignments:

auto (lof, loa) = heap.front;


Inside function signatures; notice the double (()) needed for a lambda:

map!(((c, f)) => ...)


And in foreach:

foreach ((c, ref e); loa)


Walter said that it's hard to predict what bad interactions such syntax will have with the other parts of the D syntax. I agree with this. On the other hand being frozen with fear isn't enough, the partial patch by Hara is getting dust since months. Given the importance of this feature, to break this ice and start moving forward I suggest to distribute a dmd with this experimental feature, and let people use and try it for few months or one year or more. Hopefully this will be enough to uncover the problems.

One way to do it is to just distribute a compiled experimental dmd with this feature. But this dmd will become quickly obsolete. An alternative solution is to use the idea of the Haskell GHC compiler, and introduce a compiler switch like -experimental=tuple_unpack (it's off on default). Python uses "from future import ..." for similar purposes.

I think it's good to have such staging in D for new ideas (I think it will be less used than in GHC). Such staging will avoid design mistakes like @[]  @() for UDAs and similar. Having a feature implemented in a GitHub patch is not enough, because only 1-5 persons try it for a very short time. And putting a feature in DMD to eventually remove it in a successive DMD if it's bad (like the built-in regular expression syntax experiment) can't work any more at the current advanced stage of the D development.

Bye,
bearophile
March 21, 2013
I would like to see tuple syntax and abilities improved. It's been a while since I last tried to use them so I'm not prepared to explain in detail what I'd like to see for improvements, however I can say that when I did try to use them I remember they were much more unwieldy to use and more limited that I thought was necessary.

I tried to use them for named parameters and for a function that could return multiple results, both without resorting to structs, but those attempts failed.

I basically wrote tuples off as too complicated and awkward to bother with and have not attempted to use them since, but that is unfortunate.

--rt
March 22, 2013
22.03.2013 1:13, bearophile пишет:
> And in foreach:
>
> foreach ((c, ref e); loa)


D already have undocumented[1] front tuple expansion in foreach over range:
---
import std.algorithm;
import std.stdio;

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

[1] http://d.puremagic.com/issues/show_bug.cgi?id=7361

-- 
Денис В. Шеломовский
Denis V. Shelomovskij
March 22, 2013
22-Mar-2013 11:05, Denis Shelomovskij пишет:
> 22.03.2013 1:13, bearophile пишет:
>> And in foreach:
>>
>> foreach ((c, ref e); loa)
>
>
> D already have undocumented[1] front tuple expansion in foreach over range:
> ---
> import std.algorithm;
> import std.stdio;
>
> void main() {
>      enum s = "this is an example for huffman encoding"d;
>      foreach (c, n; s.dup.sort().release().group())
>          writefln("'%s'  %s", c, n);
> }
> ---
>
> [1] http://d.puremagic.com/issues/show_bug.cgi?id=7361
>

Nice, but where is the freaking manual :)

-- 
Dmitry Olshansky
March 22, 2013
On 2013-03-21 22:13, bearophile wrote:

> auto (lof, loa) = heap.front;

How is front suddenly returning two elements?

-- 
/Jacob Carlborg
March 22, 2013
Denis Shelomovskij:

> D already have undocumented[1] front tuple expansion in foreach over range:
> ---
> import std.algorithm;
> import std.stdio;
>
> void main() {
>     enum s = "this is an example for huffman encoding"d;
>     foreach (c, n; s.dup.sort().release().group())
>         writefln("'%s'  %s", c, n);
> }


I know, but it doesn't work in the case of my code I have shown, with a SortedRange of 2-tuples:

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

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

Jacob Carlborg:

>> auto (lof, loa) = heap.front;
>
> How is front suddenly returning two elements?

It is not returning two elements. In D you can't return two elements. It is returning a 2-Tuple. You see the 2-tuples are built by the map() and then used to create a heap:

auto heap = sf.map!(((c, f)) => tuple(f, ...)).array.heapify!q{...};


And this proposed syntax performs pattern matching on that 2-tuple, unpacking it into two variables (Hara has implemented this already):

auto (lof, loa) = ...;

In Haskell, Scala, Python, F#, etc, the semantics and syntax are similar.

Bye,
bearophile
March 22, 2013
22-Mar-2013 14:58, bearophile пишет:

>
> Jacob Carlborg:
>
>>> auto (lof, loa) = heap.front;
>>
>> How is front suddenly returning two elements?
>
> It is not returning two elements. In D you can't return two elements. It
> is returning a 2-Tuple. You see the 2-tuples are built by the map() and
> then used to create a heap:
>
> auto heap = sf.map!(((c, f)) => tuple(f, ...)).array.heapify!q{...};
>
>
> And this proposed syntax performs pattern matching on that 2-tuple,
> unpacking it into two variables (Hara has implemented this already):
>
> auto (lof, loa) = ...;
>
> In Haskell, Scala, Python, F#, etc, the semantics and syntax are similar.
>

I'd hate any solution that distances ordinary structs, library tuples and fixed-sized arrays. If anything these should be trivially substitutable where makes sense.

Thus I'd go for struct destructuring syntax like (god forgive) JS6.

I even had proposal to about same effect, but it was ignored. Among other things it replace .tupleof with more general destructuring facility.

-- 
Dmitry Olshansky
March 22, 2013
On Friday, 22 March 2013 at 07:05:03 UTC, Denis Shelomovskij wrote:
> 22.03.2013 1:13, bearophile пишет:
>> And in foreach:
>>
>> foreach ((c, ref e); loa)
>
>
> D already have undocumented[1] front tuple expansion in foreach over range:
> ---
> import std.algorithm;
> import std.stdio;
>
> void main() {
>     enum s = "this is an example for huffman encoding"d;
>     foreach (c, n; s.dup.sort().release().group())
>         writefln("'%s'  %s", c, n);
> }
> ---
>
> [1] http://d.puremagic.com/issues/show_bug.cgi?id=7361

I've run in to this a few times and it's very irritating. The lack of an explicit syntax introduces an ambiguity:

foreach (c, n; s.dup.sort().release().group())
    writefln("'%s'  %s", c, n);

should c be the index, or should the tuple be unpacked? Currently the tuple is unpacked. In the general case, there is no easy way of having n be the tuple and c be the size_t index.

Sure, you could write

foreach (size_t c, n; s.dup.sort().release().group())
    writefln("'%s'  %s", c, n);

forcing n to be a tuple, which I think should work ( but doesn't, with a lot of irrelevant error messages: http://dpaste.dzfl.pl/7589a472 ), but if the first element of the tuple is also type size_t then you have a true ambiguity.


tldr: If we're going to have tuple unpacking, we need some way of controlling it.
March 22, 2013
Dmitry Olshansky:

>> auto (lof, loa) = ...;
>>
>> In Haskell, Scala, Python, F#, etc, the semantics and syntax are similar.
>>
>
> I'd hate any solution that distances ordinary structs, library tuples and fixed-sized arrays. If anything these should be trivially substitutable where makes sense.

In Bugzilla I suggested that syntax for arrays too, I'd like that:

int[2] data = [10, 20];
auto (a, b) = data;


Regarding tuples and structs, Tuples are implemented as structs, so forbidding struct unpacking is probably more work than not doing it. On the other hand structs and tuples are not exactly the same thing, despite one is implemented with the other.


> I even had proposal to about same effect, but it was ignored. Among other things it replace .tupleof with more general destructuring facility.

I don't want to use ".tupleof" to unpack a Tuple. Because it's a very commonly done operation, so it needs a short syntax.

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

John Colvin:

> tldr: If we're going to have tuple unpacking, we need some way of controlling it.

I agree, that's why in my proposed code I have added parentheses:

foreach ((c, ref e); loa)

foreach ((c, e); s.dup.sort().release.group.encode)

Bye,
bearophile
March 22, 2013
>> foreach ((c, ref e); loa)

I think parens become rather confusing, what about something like:

foreach (|c, ref e|; loa)
« First   ‹ Prev
1 2