Skip to content

Conversation

@wilzbach
Copy link
Contributor

Motivation: Named tuples are a lot better to read.

There has been a precedent for this, see #5436

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @wilzbach!

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Copy link
Member

@quickfur quickfur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. A lot. Anonymous tuples are hard to remember (is the haystack t[0] or t[1]?) and make user code more obtuse.

However, the named fields really really need to be described thoroughly in the ddoc header of each respective function. It's not good enough to just stick that into the return type.

(On that note, I'm a bit sad that the return type spec is getting long and scary to read... but that's an issue for another day, and out of scope for this PR. :-P)

@MetaLang
Copy link
Member

MetaLang commented Dec 28, 2017

I'm not sure about this. On one hand, it's technically a breaking change because named Tuples don't implicitly convert to un-named Tuples:

void doSomethingWithFindResult(Tuple!(int[], size_t) t);
doSomethingWithFindResult([ 1, 4, 2, 3 ].find(2, 4)); //Error: cannot convert type Tuple!(int[], "haystack", size_t, "range") to Tuple!(int[], size_t)

On the other hand, I doubt this type of code is very common.

@JackStouffer
Copy link
Contributor

This is also more dubious that the linked PR because in that case the return type was auto, which means you shouldn't expect the return type to be name-able in your code. However, the return type is explicitly documented here, and is a more serious breaking change.

Personally, I think the breakage will be at a minimum, but it will still exist.

Paging @andralex for his opinion.

@wilzbach
Copy link
Contributor Author

First things first: I split this PR of into three parts:

On one hand, it's technically a breaking change because named Tuples don't implicitly convert to un-named Tuples:

Yep, but note that this is small subset - even comparison works:

assert(find(a, [ 1, 3 ], 4) == tuple([ 4, 2, 3 ], 2));

However, the return type is explicitly documented here, and is a more serious breaking change.

For findSplit (#5968) we already used auto

However, the named fields really really need to be described thoroughly in the ddoc header of each respective function. It's not good enough to just stick that into the return type.

Done. Especially I like the improvement of the documentation for findSplit.

Personally, I think the breakage will be at a minimum, but it will still exist.
...
I like this. A lot. Anonymous tuples are hard to remember

Can't say more than this. I think in this case the gain outweighs the minimal cases where we might break code.

@wilzbach wilzbach force-pushed the searching branch 3 times, most recently from 8b7fb6f to ccaead0 Compare December 28, 2017 23:40
@quickfur
Copy link
Member

I'm tempted to say that find should return auto. This PR is a living proof of why explicit return types is a bad idea in generic library code. Had we written find to return auto in the first place, we wouldn't have risked this kind of breakage in user code. Now our hands are tied.


/**
Finds two or more $(D needles) into a $(D haystack). The predicate $(D
Finds two or more `needles` into a `haystack`. The predicate $(D
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving the changes of $(D...) to backticks to a different PR that we can merge immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's better follow-up on the various requests to use backticks in the docs and do it for std.algorithm once and for all?
-> #5970

For variadic overloads `std.algorithm.searching.find` will
return a named tuple tuple instead of an anonymous tuple.
std/typecons.d(648): Error: reinterpreting cast from inout(string)* to inout(Tuple!(string, ulong))* is not supported in CTFE
std/algorithm/searching.d(2538):        called from here: find(haystack, _param_1, _param_2)._Tuple_super()
std/typecons.d(977):        called from here: canFind("dat", "pos", k)
std/algorithm/iteration.d(1148):        called from here: __lambda1(front(this._input))
std/algorithm/iteration.d(1174):        called from here: this.prime()
std/typecons.d(978):        called from here: FilterResult(["dat"], false, null).empty()
std/typecons.d(978):        while evaluating: static assert(FilterResult(["dat"], false, null).empty)
std/typecons.d(1021): Error: template instance std.typecons.Tuple!(float, "dat", ulong[2], "pos").Tuple.rename!(["dat":"height"]) error instantiating
std/typecons.d(1378):        instantiated from here: Tuple!()
@n8sh
Copy link
Member

n8sh commented Jan 11, 2018

On one hand, it's technically a breaking change because named Tuples don't implicitly convert to un-named Tuples

Don't they? This runs:
https://run.dlang.io/is/qTspmv

void main(string[] args)
{
    import std.typecons : Tuple;
    alias Point = Tuple!(int, int);
    alias PointNamed = Tuple!(int, "x", int, "y");

    import std.traits : isImplicitlyConvertible;
    static assert(isImplicitlyConvertible!(PointNamed, Point));
    static assert(!isImplicitlyConvertible!(Point, PointNamed));

    import std.stdio : writeln;
    writeln(PointNamed.stringof, " is implicitly convertible to ", Point.stringof,
            " but not vice versa.");
}

@MetaLang
Copy link
Member

MetaLang commented Jan 11, 2018

Hmm... you're right, but now I don't understand why. Maybe it's because they're structs that have the same layout, so the compiler allows them to implicitly convert to each other...?

import std.typecons;

Tuple!(int, "x", int, "y") test(int x, int y) { return tuple!("x", "y")(x, y); }
void takesTup(Tuple!(int, int)) {}

void main()
{
    auto t = test(1, 1);
    takesTup(t);
    takesTup(test(2, 3));
    takesTup(tuple!("x", "y")(2, 2));
}

@quickfur
Copy link
Member

ping @andralex This is a potentially breaking change, need your review & approval.

@andralex
Copy link
Member

I wish we just closed this. It created just work for everyone involved, and is liable to create more.

@quickfur
Copy link
Member

Should we just close this then?

@wilzbach
Copy link
Contributor Author

I wish we just closed this. It created just work for everyone involved, and is liable to create more.

Would you mind elaborating on this a bit?
find(a, 2, 4).needle is a lot easier to read than find(a, 2, 4)[1] and the breakage is more hypothetical / minimal.
When we did our last "Learn D session" at the Munich Meetup group, usage of anonymous tuples in the Phobos APIs was among the common complaints (hence this PR btw).

@andralex
Copy link
Member

I don't deny it's nicer. But there's work in reviewing, work in fixing known breakages of existing code, and more future work on fixing other breakages. It doesn't seem on the right side of cost/reward.

@andralex
Copy link
Member

Gave this a fresh look, I can't shed the feeling "why are we bothering with low impact matter when there's so many big rocks to move". Anyhow, what are the cases that break - aside from those that explicitly check for exact typeof. The answer to this will inform this PR and others like it,

@MetaLang
Copy link
Member

what are the cases that break

@andralex I'm surprised at what the compiler allows; these are the cases I could think of OTOH:

import std.typecons;

Tuple!(int, "x", int, "y") test(int x, int y) { return tuple!("x", "y")(x, y); }
void takesTup(Tuple!(int, int)) {}
void takesTup2(Tuple!(int, "asdf", int, "fdsa"));

void main()
{
    auto t = test(1, 1);
    takesTup(t); //ok
    takesTup(test(2, 3)); //ok
    takesTup(tuple!("x", "y")(2, 2)); //ok
    
    Tuple!(int, int) t2 = t; //ok
    Tuple!(int, "asdf", int, "fdsa") t3 = t; //ok
    Tuple!(int, "asdf", int, "fdsa") t4 = Tuple!(int, "x", int, "y")(1, 1); //ok
    
    Tuple!(int, int) t5 = Tuple!(int, "x", int, "y")(1, 1); //ok
    takesTup2(t);  //error
    takesTup2(t2); //error
}

@andralex
Copy link
Member

@MetaLang https://github.com/dlang/phobos/blob/master/std/typecons.d#L651 has a lot to do with this. Named tuples are subtypes of their unnamed counterparts.

With this realization I think this PR is a go. Any breakage anyone sees?

Copy link
Member

@andralex andralex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm for this. If it causes major issues we didn't get all aspects of alias this right.

@jmdavis
Copy link
Member

jmdavis commented Feb 12, 2018

As discussed in this PR, changing findSplit to return a named tuple broke code, because t[0] and t.name weren't actually equivalent like they should be. So, until we figure out why and fix Tuple, I don't see how we can reasonably change any anonymous tuples to named tuples.

Such a change should work as long as you ignore the issue of anyone who explicitly declared a Tuple based on the docs instead of using typeof having problems (and we can probably safely ignore that), but apparently, it does currently break some code that simply was indexing the tuple.

@andralex
Copy link
Member

@jmdavis can you please explain why t[0] is not equivalent to t.name? That would indeed be a problem. The visible comments in #6078 discuss the aftermath of the matter, I can't seem to see it stated.

@jmdavis
Copy link
Member

jmdavis commented Feb 12, 2018

Well, that discussion linked to one of the changes that had to be made to make Phobos compile when findSplit was made to return named tuples, which was this:

8becc70

Based on the commit message, it seems to be a problem related to CTFE and casting. I did do some looking over Tuple to try and understand it when that PR was posted, but I didn't spend enough time at it to fully dissect what was going on or figure out how to fix it.

Given that t[0] works correctly when the tuple is not named, and t.name works when the tuple is named, I would assume that the problem is fixable, but Tuple's implementation is quite complicated.

@andralex
Copy link
Member

Got it, thanks. That's actually an easy fix - we need to use a non-names Tuple as backend for the respective named Tuple. Then no need to cast in CTFE. I've submitted https://issues.dlang.org/show_bug.cgi?id=18426

@wilzbach
Copy link
Contributor Author

Based on the commit message, it seems to be a problem related to CTFE and casting. I did do some looking over Tuple to try and understand it when that PR was posted,

I dived a bit deeper and reduced the problem to this example:

https://run.dlang.io/gist/9e497b0205031ee8370aa9fa1ea18399?compiler=dmd&args=-unittest%20-main%20-c

However, I'm not sure how we can workaround this. If just the alias expand this is used, this stops to work:

auto t1 = Tuple!(int, "x", string, "y")(1, "a");
void foo(Tuple!(int, string) t2) {}
foo(t1);
foo.d(149): Error: function foo.__unittest_L143_C7.foo(Tuple!(int, string) t2) is not callable using argument types (Tuple!(int, "x", string, "y"))
foo.d(149):        cannot pass argument t1 of type Tuple!(int, "x", string, "y") to parameter Tuple!(int, string) t2

So @property ref inout(Tuple!Types) _Tuple_super() inout @trusted is the magic enemy here :/

@andralex
Copy link
Member

So @Property ref inout(Tuple!Types) _Tuple_super() inout @trusted is the magic enemy he

It's not the signature, it's the implementation. All we need is to get rid of that cast, and we do so by reusing the unnamed Tuple as layout for the named Tuple.

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 12, 2018

we do so by reusing the unnamed Tuple as layout for the named Tuple.

That's already the case today - even for named tuples the layout is as follows:

struct Tuple
{
    Types expand; // AliasSeq!(int, int)
}

@wilzbach
Copy link
Contributor Author

wilzbach commented Feb 12, 2018

we need to do this:
Tuple!(int, "x", int, "y") should have as its only state Tuple!(int, int). In the form of a data member that is. Call that field impl. Then:
alias impl this;

This runs into the same mentioned overload problems as just Types expand

https://run.dlang.io/gist/9041fb8eac5134ffd47f0acbb2f4fb26?compiler=dmd&args=-unittest%20-main%20-c

(updated link)

@andralex
Copy link
Member

@wilzbach link doesn't work but at any rate we can make it work.

@LightBender LightBender added the Review:Phantom Zone Has value/information for future work, but closed for now label Oct 26, 2024
@LightBender
Copy link
Contributor

PR closed as stalled. If you wish to resurrect it you are welcome to do so. @tgehr This may be of interest to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Merge:stalled Review:Phantom Zone Has value/information for future work, but closed for now

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants