phobos
phobos copied to clipboard
find: return named tuples instead of anonymous
Motivation: Named tuples are a lot better to read.
There has been a precedent for this, see https://github.com/dlang/phobos/pull/5436
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.
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.
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.
First things first: I split this PR of into three parts:
find(this PR)findSplit(#5968)- the typo + removed
std.stdiologging (#5969)
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.
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.
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.");
}
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));
}
ping @andralex This is a potentially breaking change, need your review & approval.
I wish we just closed this. It created just work for everyone involved, and is liable to create more.
Should we just close this then?
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).
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.
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,
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
}
@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?
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.
@jmdavis can you please explain why t[0] is not equivalent to t.name? That would indeed be a problem. The visible comments in https://github.com/dlang/phobos/pull/6078 discuss the aftermath of the matter, I can't seem to see it stated.
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:
https://github.com/dlang/phobos/commit/8becc70da790826bccec734d40021dd7ff22f485
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.
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
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 :/
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.
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)
}
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)
@wilzbach link doesn't work but at any rate we can make it work.