New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Champion "Support for == and != on tuple types" (15.7) #190

Open
gafter opened this Issue Feb 26, 2017 · 72 comments

Comments

Projects
None yet
@gafter
Member

gafter commented Feb 26, 2017

Summary

Support == and != on tuples.
For example (x, y) == (1, 2) (which would be equivalent to x == 1 && y == 2).

  • Proposal added (#967)
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

See also dotnet/roslyn#13155

LDM history:

Started prototype at tuple-equality

@gafter gafter added this to the 7.X candidate milestone Feb 26, 2017

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Feb 27, 2017

Should just support extension operator on IEquatable<T>

Thaina commented Feb 27, 2017

Should just support extension operator on IEquatable<T>

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Feb 27, 2017

Member

@Thaina No, == and .Equals are different things. Try double.NaN for example. Also, the == operator would convert the values in a tuple, elementwise, to a common type for comparison purposes, while .Equals requires that they be the same type.

Member

gafter commented Feb 27, 2017

@Thaina No, == and .Equals are different things. Try double.NaN for example. Also, the == operator would convert the values in a tuple, elementwise, to a common type for comparison purposes, while .Equals requires that they be the same type.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 28, 2017

Retracting my up-vote for this proposal. As I read "No, == and .Equals are different things", I do not just witness the principle of least astonishment being violated, it's being smashed into tiny pieces and ground into the dirt.

Yes, for historical reasons, we have ended up with multiple ways of testing for equality and this is one of those "we are stuck with it" things that can't be undone. But perpetuating this messy situation by having new types like ValueTuple<...> implement different behaviour for == and .Equals is pure madness IMO.

We would be better off with the team just leaving this topic well alone and implementing the "extension everything" proposal instead. Then 3rd party libraries can implement == and .Equals as the same thing and the great disturbance in the force will fade away...

DavidArno commented Feb 28, 2017

Retracting my up-vote for this proposal. As I read "No, == and .Equals are different things", I do not just witness the principle of least astonishment being violated, it's being smashed into tiny pieces and ground into the dirt.

Yes, for historical reasons, we have ended up with multiple ways of testing for equality and this is one of those "we are stuck with it" things that can't be undone. But perpetuating this messy situation by having new types like ValueTuple<...> implement different behaviour for == and .Equals is pure madness IMO.

We would be better off with the team just leaving this topic well alone and implementing the "extension everything" proposal instead. Then 3rd party libraries can implement == and .Equals as the same thing and the great disturbance in the force will fade away...

@ig-sinicyn

This comment has been minimized.

Show comment
Hide comment
@ig-sinicyn

ig-sinicyn Feb 28, 2017

@DavidArno

We would be better off with the team just leaving this topic well alone and implementing the "extension everything" proposal instead. Then 3rd party libraries can implement == and .Equals as the same thing and the great disturbance in the force will fade away...

so, double.Nan == double.Nan should be false but
(double.Nan, double.Nan) == (double.Nan, double.Nan)
will be true, false, NaN or a tiny pink elephant depending on a developer's preferences? Pure evil 👍

ig-sinicyn commented Feb 28, 2017

@DavidArno

We would be better off with the team just leaving this topic well alone and implementing the "extension everything" proposal instead. Then 3rd party libraries can implement == and .Equals as the same thing and the great disturbance in the force will fade away...

so, double.Nan == double.Nan should be false but
(double.Nan, double.Nan) == (double.Nan, double.Nan)
will be true, false, NaN or a tiny pink elephant depending on a developer's preferences? Pure evil 👍

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 28, 2017

Contributor

Tuples being just a loosely bound sequence of values I think that it makes more sense to be able to compare those values irrespective of the container.

(int, int) tuple1 = ...;
(int, int) tuple2 = ...;

if (tuple1 == tuple2) { ... }
// the same as
if ((tuple1.Item1 == tuple2.Item1 && tuple1.Item2 == tuple2.Item2)) { ... }
Contributor

HaloFour commented Feb 28, 2017

Tuples being just a loosely bound sequence of values I think that it makes more sense to be able to compare those values irrespective of the container.

(int, int) tuple1 = ...;
(int, int) tuple2 = ...;

if (tuple1 == tuple2) { ... }
// the same as
if ((tuple1.Item1 == tuple2.Item1 && tuple1.Item2 == tuple2.Item2)) { ... }
@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 28, 2017

@ig-sinicyn,

In the following code, result should be true:

var x = double.Nan;
var y = double.Nan;
var result = (x == y) == x.Equals(y);

But it's false as double implements == and .Equals differently.

You appear to be proposing that, just because the CLR and language teams couldn't agree on whether NaN is the same as NaN, then tuples equality should perpetuate this nonsense. Having (double.Nan, double.Nan) == (double.Nan, double.Nan) would be a tiny price to pay for equality sanity in tuples.

DavidArno commented Feb 28, 2017

@ig-sinicyn,

In the following code, result should be true:

var x = double.Nan;
var y = double.Nan;
var result = (x == y) == x.Equals(y);

But it's false as double implements == and .Equals differently.

You appear to be proposing that, just because the CLR and language teams couldn't agree on whether NaN is the same as NaN, then tuples equality should perpetuate this nonsense. Having (double.Nan, double.Nan) == (double.Nan, double.Nan) would be a tiny price to pay for equality sanity in tuples.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 28, 2017

@HaloFour,

struct S { }

(S, S) tuple1 = ...;
(S, S) tuple2 = ...;

if (tuple1.Item1 == tuple2.Item1 && 
    tuple1.Item2 == tuple2.Item2) // oh dear: compiler error

ValueTuple<T1, T2> cannot implement == as the above as it's not constrained to T1 and T2 being classes.

DavidArno commented Feb 28, 2017

@HaloFour,

struct S { }

(S, S) tuple1 = ...;
(S, S) tuple2 = ...;

if (tuple1.Item1 == tuple2.Item1 && 
    tuple1.Item2 == tuple2.Item2) // oh dear: compiler error

ValueTuple<T1, T2> cannot implement == as the above as it's not constrained to T1 and T2 being classes.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 28, 2017

Contributor

@DavidArno

Yes, and I have no problem with that. I don't think that the fact that the two values are contained within a tuple should matter.

Contributor

HaloFour commented Feb 28, 2017

@DavidArno

Yes, and I have no problem with that. I don't think that the fact that the two values are contained within a tuple should matter.

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Feb 28, 2017

@DavidArno Did you know that comparison operators and CompareTo produce different results on doubles as well? That's not something that .Net invented either. Comparison and total order operations are defined differently on doubles by IEEE. Equals matches the behavior of CompareTo.

orthoxerox commented Feb 28, 2017

@DavidArno Did you know that comparison operators and CompareTo produce different results on doubles as well? That's not something that .Net invented either. Comparison and total order operations are defined differently on doubles by IEEE. Equals matches the behavior of CompareTo.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 28, 2017

@orthoxerox, @HaloFour, @ig-sinicyn,

So you have all done a lot of "no, no, can't be .Equals() and @gafter has ruled out the IEquatable<T>/EqualityComparer route and == on T1 etc can't be used. So what solution is left?

DavidArno commented Feb 28, 2017

@orthoxerox, @HaloFour, @ig-sinicyn,

So you have all done a lot of "no, no, can't be .Equals() and @gafter has ruled out the IEquatable<T>/EqualityComparer route and == on T1 etc can't be used. So what solution is left?

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 28, 2017

Contributor

@DavidArno

The solution is that the developer has to use Equals manually rather than relying on == or !=, which is the same answer as when tuples aren't involved.

Contributor

HaloFour commented Feb 28, 2017

@DavidArno

The solution is that the developer has to use Equals manually rather than relying on == or !=, which is the same answer as when tuples aren't involved.

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Feb 28, 2017

@DavidArno the compiler has to rewrite the left == right expression as (left.Item1 == right.Item1 && left.Item2 == right.Item2). If one of the types is a struct without ==, it will be a compiler error.

orthoxerox commented Feb 28, 2017

@DavidArno the compiler has to rewrite the left == right expression as (left.Item1 == right.Item1 && left.Item2 == right.Item2). If one of the types is a struct without ==, it will be a compiler error.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 28, 2017

@orthoxerox,

The following would have to be a compiler error therefore, as == in tuples would be a compiler trick, rather than a static operator on the type and so it cannot determine the type at compile-time:

bool F<T1,T2>((T1, T2) x, (T1, T2) y) => x == y;

DavidArno commented Feb 28, 2017

@orthoxerox,

The following would have to be a compiler error therefore, as == in tuples would be a compiler trick, rather than a static operator on the type and so it cannot determine the type at compile-time:

bool F<T1,T2>((T1, T2) x, (T1, T2) y) => x == y;
@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Feb 28, 2017

@DavidArno it's an error now and I don't see how it is related to equality of tuples.

orthoxerox commented Feb 28, 2017

@DavidArno it's an error now and I don't see how it is related to equality of tuples.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 28, 2017

@orthoxerox,

Apologies: the code was wrong. I've updated it to what I meant to say.

DavidArno commented Feb 28, 2017

@orthoxerox,

Apologies: the code was wrong. I've updated it to what I meant to say.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 28, 2017

Contributor

@DavidArno

So, how about the compiler first attempts to resolve == operators defined on ValueTuple<...> via extensions and if one isn't found it instead falls back to evaluating the elements individually?

I don't know that I like that, though. If you needed the container of those two values to matter, enough to define their equality, then you probably shouldn't be using tuples and you should define a proper type instead.

Contributor

HaloFour commented Feb 28, 2017

@DavidArno

So, how about the compiler first attempts to resolve == operators defined on ValueTuple<...> via extensions and if one isn't found it instead falls back to evaluating the elements individually?

I don't know that I like that, though. If you needed the container of those two values to matter, enough to define their equality, then you probably shouldn't be using tuples and you should define a proper type instead.

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Feb 28, 2017

@DavidArno Yes, it looks like it will have to be a compiler error, just like this function is right now:

bool F<T1,T2>(T1 x1, T2 x2, T1 y1, T2 y2) => x1 == y1 && x2 == y2;

P.S. I've just spent an hour enumerating all the equality mechanisms of the CLR and I've yet to cover the interfaces.

orthoxerox commented Feb 28, 2017

@DavidArno Yes, it looks like it will have to be a compiler error, just like this function is right now:

bool F<T1,T2>(T1 x1, T2 x2, T1 y1, T2 y2) => x1 == y1 && x2 == y2;

P.S. I've just spent an hour enumerating all the equality mechanisms of the CLR and I've yet to cover the interfaces.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

You appear to be proposing that, just because the CLR and language teams couldn't agree on whether NaN is the same as NaN

That's not what's happening.

== implement IEEE754 semantics. .Equals impelments .Net equals semantics. IEEE requires that == not be reflexive for NaN. However, .Equals is required to be reflexive. i.e. object.Equals states that "a.Equals(a)" must be true for all instances.

If we broke IEEE semantics that would be a problem for tons of customers. If we broke .net semantics that would be a problem for tons of .net use cases (for example, you could not use a double effectively as a key inside a hashtable). This approach gives both sets of customers the semantics they require.

CyrusNajmabadi commented Mar 1, 2017

You appear to be proposing that, just because the CLR and language teams couldn't agree on whether NaN is the same as NaN

That's not what's happening.

== implement IEEE754 semantics. .Equals impelments .Net equals semantics. IEEE requires that == not be reflexive for NaN. However, .Equals is required to be reflexive. i.e. object.Equals states that "a.Equals(a)" must be true for all instances.

If we broke IEEE semantics that would be a problem for tons of customers. If we broke .net semantics that would be a problem for tons of .net use cases (for example, you could not use a double effectively as a key inside a hashtable). This approach gives both sets of customers the semantics they require.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

ValueTuple<...> implement different behaviour for == and .Equals is pure madness IMO.

No, it really isn't. For example, today, if i have:

byte b = 1;
int i = 1;

Console.WriteLine(b == i);
Console.WriteLine(b.Equals(i));

I will print "True, False".

== and .Equals are already very different.

The problem compounds with tuples. Tuples just aggregate data. If you aggregate data that uses to compare the same with == but now compares differently (or vice versa), then tuples no longer act properly a composition concept.

CyrusNajmabadi commented Mar 1, 2017

ValueTuple<...> implement different behaviour for == and .Equals is pure madness IMO.

No, it really isn't. For example, today, if i have:

byte b = 1;
int i = 1;

Console.WriteLine(b == i);
Console.WriteLine(b.Equals(i));

I will print "True, False".

== and .Equals are already very different.

The problem compounds with tuples. Tuples just aggregate data. If you aggregate data that uses to compare the same with == but now compares differently (or vice versa), then tuples no longer act properly a composition concept.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

Using hte above example, if we do not support == on tuples then you can get the following badness:

byte b;
string s;

(byte, string) t1 = (b, s);
(int, string) t2 = (b, s);

Console.WriteLine(t1.Item1 == t2.Item1 && t2.Item2 == t2.Item2);
Console.WriteLine(t1 == t2); // doesn't currently compile.  But we would like it to.  Can't be implemented through .Equals
Console.WriteLine(t1.Equals(t2));

The top and bottom lines will print 'true', and 'false'. I strongly believe the second line shoudl print 'true' and that it should just be exactly transformed as:

t1 == t2 
// becomes
t1.Item1 == t2.Item1 && ... && t1.ItemN == t2.ItemN

This, of course, then means that nested tuples work properly (something that definitely does not work with .Equals).

CyrusNajmabadi commented Mar 1, 2017

Using hte above example, if we do not support == on tuples then you can get the following badness:

byte b;
string s;

(byte, string) t1 = (b, s);
(int, string) t2 = (b, s);

Console.WriteLine(t1.Item1 == t2.Item1 && t2.Item2 == t2.Item2);
Console.WriteLine(t1 == t2); // doesn't currently compile.  But we would like it to.  Can't be implemented through .Equals
Console.WriteLine(t1.Equals(t2));

The top and bottom lines will print 'true', and 'false'. I strongly believe the second line shoudl print 'true' and that it should just be exactly transformed as:

t1 == t2 
// becomes
t1.Item1 == t2.Item1 && ... && t1.ItemN == t2.ItemN

This, of course, then means that nested tuples work properly (something that definitely does not work with .Equals).

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 1, 2017

Ha, ha, thanks @CyrusNajmabadi, the idea of someone trying to use a double as a key in a hashtable is going to keep me chuckling all day! 😁 Even JavaScript, where it'll let black equal white every other Thursday except on leap years doesn't treat NaN as equal. The idea that the .NET team's dogma was so strong that they'd break an international standard just to enable doubles to be used as Dictionary keys is mind-boggling.

Anyway, that aside, you didn't address the important point from above. If a value tuples' types are structs, they may not implement == themselves. So how would you propose this feature work?

DavidArno commented Mar 1, 2017

Ha, ha, thanks @CyrusNajmabadi, the idea of someone trying to use a double as a key in a hashtable is going to keep me chuckling all day! 😁 Even JavaScript, where it'll let black equal white every other Thursday except on leap years doesn't treat NaN as equal. The idea that the .NET team's dogma was so strong that they'd break an international standard just to enable doubles to be used as Dictionary keys is mind-boggling.

Anyway, that aside, you didn't address the important point from above. If a value tuples' types are structs, they may not implement == themselves. So how would you propose this feature work?

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

the idea of someone trying to use a double as a key in a hashtable is going to keep me chuckling all day!

Using a double as a key is completely fine to do.

The idea that the .NET team's dogma was so strong that they'd break an international standard just to enable doubles to be used as Dictionary keys is mind-boggling.

It's interesting that you complain about things being inconsistent, but then complain when .net insisted that .Equals behave consistently :)

Anyway, that aside, you didn't address the important point from above. If a value tuples' types are structs, they may not implement == themselves. So how would you propose this feature work?

The exact way that == works for types that don't have an available == operator. I'm stating that == for tuples is defined as:

(x1, ..., xn) == (y1, ..., yn)   <==>  x1 == y1 && ... && xn == yn

== on the tuple will work in precisely all the circumstances that == works for the constituent elements.

just to enable doubles to be used as Dictionary keys is mind-boggling.

I never said that. Please do not put words in my mouth. I simply gave an example of how the .Equals behavior is desirable and consistent. I did not say that it was done "just" for this reason.

--

Also, to be clear, you never addressed the other points i made. For example, that == and .Equals are not equivalent today for ordinary numeric types other than double. Today == will return true when .Equals does not. == follows the rules of C# while .Equals follows the rules of .Net. Making it so that we have a type in C# which composes other C# types, but which does not compose == appropriately just leads to the composition not actually happening fully.

CyrusNajmabadi commented Mar 1, 2017

the idea of someone trying to use a double as a key in a hashtable is going to keep me chuckling all day!

Using a double as a key is completely fine to do.

The idea that the .NET team's dogma was so strong that they'd break an international standard just to enable doubles to be used as Dictionary keys is mind-boggling.

It's interesting that you complain about things being inconsistent, but then complain when .net insisted that .Equals behave consistently :)

Anyway, that aside, you didn't address the important point from above. If a value tuples' types are structs, they may not implement == themselves. So how would you propose this feature work?

The exact way that == works for types that don't have an available == operator. I'm stating that == for tuples is defined as:

(x1, ..., xn) == (y1, ..., yn)   <==>  x1 == y1 && ... && xn == yn

== on the tuple will work in precisely all the circumstances that == works for the constituent elements.

just to enable doubles to be used as Dictionary keys is mind-boggling.

I never said that. Please do not put words in my mouth. I simply gave an example of how the .Equals behavior is desirable and consistent. I did not say that it was done "just" for this reason.

--

Also, to be clear, you never addressed the other points i made. For example, that == and .Equals are not equivalent today for ordinary numeric types other than double. Today == will return true when .Equals does not. == follows the rules of C# while .Equals follows the rules of .Net. Making it so that we have a type in C# which composes other C# types, but which does not compose == appropriately just leads to the composition not actually happening fully.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

Note: having .Equals be symmetric is nice for lots of reasons. After all, while it would be very weird to have "a.Equals(a)" be false, it would be even more confusing and difficult to use .net effectively if doubles didn't support this concept. After all, the following could then be false:

list.Add(d);
Console.WriteLine(list.Contains(d))

Effectively you would make it so that doubles could not actually work effectively as any sort of object in any sort of container. You could not use them effectively in linq. They would always be something that never meshed well with the rest of .Net. So .Net implements .Net semantics. If you want IEEE semantics, you can use the simple operators.

CyrusNajmabadi commented Mar 1, 2017

Note: having .Equals be symmetric is nice for lots of reasons. After all, while it would be very weird to have "a.Equals(a)" be false, it would be even more confusing and difficult to use .net effectively if doubles didn't support this concept. After all, the following could then be false:

list.Add(d);
Console.WriteLine(list.Contains(d))

Effectively you would make it so that doubles could not actually work effectively as any sort of object in any sort of container. You could not use them effectively in linq. They would always be something that never meshed well with the rest of .Net. So .Net implements .Net semantics. If you want IEEE semantics, you can use the simple operators.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

I'd like the table the Double discussion as the interesting and esoteric behaviors of Double are not what are important here. All the reasons for tuples needing == support can be explained with simpler types like int/long.

For people who don't think == should be lifted to tuples, can i ask why you think the following should behave differently:

int i = 0;
long j = 0;

Console.WriteLine(i == j); // what do you think this should print?

(int, int) t1 = (i, i);
(long, long) t2 == (j, j);
Console.WriteLine(t1 == t2); // what do you think this shoudl print?

If you think the answer should be 'true' then 'false', can you explain why you think that two values which previous were == true, should now not compare the same just because they have been packaged up into a pair of values?

CyrusNajmabadi commented Mar 1, 2017

I'd like the table the Double discussion as the interesting and esoteric behaviors of Double are not what are important here. All the reasons for tuples needing == support can be explained with simpler types like int/long.

For people who don't think == should be lifted to tuples, can i ask why you think the following should behave differently:

int i = 0;
long j = 0;

Console.WriteLine(i == j); // what do you think this should print?

(int, int) t1 = (i, i);
(long, long) t2 == (j, j);
Console.WriteLine(t1 == t2); // what do you think this shoudl print?

If you think the answer should be 'true' then 'false', can you explain why you think that two values which previous were == true, should now not compare the same just because they have been packaged up into a pair of values?

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 1, 2017

@CyrusNajmabadi

int i = 0;
long j = 0;

Console.WriteLine(i == j); // what do you think this should print?

I think it should print exactly the same as for:

int i = 0;
long j = 0;

Console.WriteLine(i.Equals(j)); 

Whether it's true or false isn't important - that's just convention.

As Eric Lippert says in his Sharp Regrets: Top 10 Worst C# Features article, equality in C# is one of its top ten worst features. But what is done is done and we must live with it. However, watching you try to defend it, rather than just admitting it was a mistake that we can all learn from, is more than a little frustrating.

I'm still confused as to how you think == will work in tuples though, based on your expression:

(x1, ..., xn) == (y1, ..., yn)   <==>  x1 == y1 && ... && xn == yn

As per my previous example, struct S {}, how will the following work?

var x = (new S(), new S());
var y = (new S(), new S());
if (x == y) ...

DavidArno commented Mar 1, 2017

@CyrusNajmabadi

int i = 0;
long j = 0;

Console.WriteLine(i == j); // what do you think this should print?

I think it should print exactly the same as for:

int i = 0;
long j = 0;

Console.WriteLine(i.Equals(j)); 

Whether it's true or false isn't important - that's just convention.

As Eric Lippert says in his Sharp Regrets: Top 10 Worst C# Features article, equality in C# is one of its top ten worst features. But what is done is done and we must live with it. However, watching you try to defend it, rather than just admitting it was a mistake that we can all learn from, is more than a little frustrating.

I'm still confused as to how you think == will work in tuples though, based on your expression:

(x1, ..., xn) == (y1, ..., yn)   <==>  x1 == y1 && ... && xn == yn

As per my previous example, struct S {}, how will the following work?

var x = (new S(), new S());
var y = (new S(), new S());
if (x == y) ...
@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 1, 2017

@DavidArno What you proposed might be ideal but it could be breaking change so that's a problem. Sometimes we need to deal with legacy

Thaina commented Mar 1, 2017

@DavidArno What you proposed might be ideal but it could be breaking change so that's a problem. Sometimes we need to deal with legacy

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 1, 2017

I think it should print exactly the same as for:

Ok. Given that that's not how C# has behaved since v1, that seems bad. You are complaining about inconsistency, but would not like C# vNext to be inconsistent with C# v1-v7.

As per my previous example, struct S {}, how will the following work?

var x = (new S(), new S());
var y = (new S(), new S());
if (x == y) ...

it's exactly as i stated, it would be equivalent to:

if (x.Item1 == y.Item1 && x.Item2 == y.Item2)

If S has no == operator then i would expect an error. precisely as i would if i wrote:

var v1 = new S();
var v2 = new S();
Console.WriteLine(v1 == v2);

CyrusNajmabadi commented Mar 1, 2017

I think it should print exactly the same as for:

Ok. Given that that's not how C# has behaved since v1, that seems bad. You are complaining about inconsistency, but would not like C# vNext to be inconsistent with C# v1-v7.

As per my previous example, struct S {}, how will the following work?

var x = (new S(), new S());
var y = (new S(), new S());
if (x == y) ...

it's exactly as i stated, it would be equivalent to:

if (x.Item1 == y.Item1 && x.Item2 == y.Item2)

If S has no == operator then i would expect an error. precisely as i would if i wrote:

var v1 = new S();
var v2 = new S();
Console.WriteLine(v1 == v2);
@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 8, 2017

@HaloFour,

Ha ha, hoisted by my own petard there! Good call. 🏁

DavidArno commented Mar 8, 2017

@HaloFour,

Ha ha, hoisted by my own petard there! Good call. 🏁

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 8, 2017

Contributor

I wonder if this could be implemented on top of shapes rather than hard-coding into the compiler. Not only equality, almost all of tuple operations should be distributed over all elements (if all of them satisfy a certain set of constraints which is representable through generic shapes).

Contributor

alrz commented Mar 8, 2017

I wonder if this could be implemented on top of shapes rather than hard-coding into the compiler. Not only equality, almost all of tuple operations should be distributed over all elements (if all of them satisfy a certain set of constraints which is representable through generic shapes).

@jcouv

This comment has been minimized.

Show comment
Hide comment
@jcouv

jcouv Mar 8, 2017

Member

@DavidArno I'd like to add one clarification (related dotnet/roslyn#16869): both C# and VB languages and compilers now assume that the ValueTuple constructor is side-effect-free, so it can be skipped in some cases.
Out of curiosity, would you mind sharing some scenarios that motivate you to implement your own ValueTuple type?

Thanks @HaloFour for making the connection between those two discussions.

Member

jcouv commented Mar 8, 2017

@DavidArno I'd like to add one clarification (related dotnet/roslyn#16869): both C# and VB languages and compilers now assume that the ValueTuple constructor is side-effect-free, so it can be skipped in some cases.
Out of curiosity, would you mind sharing some scenarios that motivate you to implement your own ValueTuple type?

Thanks @HaloFour for making the connection between those two discussions.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 8, 2017

@jcouv,

I have (well had, see below) a single motivation. I don't like the fact that value tuples are mutable. The natural conclusion (in my head, anyway) was therefore to create my own - immutable - version.

However, I've reconsidered that plan, based on two things:

  1. The compiler will, from 7.1 onwards (or is it in 7?) make assumptions about the behaviour of those tuples,
  2. I read today (from a Roslyn issue you were involved in, I think, but I can't find it now), that from eg .NET framework v4.7, the ValueTuple and new async feature nuget packages will be baked into the framework.

Therefore, I no longer consider it wise to create a custom ValueTuple implementation.

DavidArno commented Mar 8, 2017

@jcouv,

I have (well had, see below) a single motivation. I don't like the fact that value tuples are mutable. The natural conclusion (in my head, anyway) was therefore to create my own - immutable - version.

However, I've reconsidered that plan, based on two things:

  1. The compiler will, from 7.1 onwards (or is it in 7?) make assumptions about the behaviour of those tuples,
  2. I read today (from a Roslyn issue you were involved in, I think, but I can't find it now), that from eg .NET framework v4.7, the ValueTuple and new async feature nuget packages will be baked into the framework.

Therefore, I no longer consider it wise to create a custom ValueTuple implementation.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 8, 2017

Contributor

@alrz

I wonder if this could be implemented on top of shapes rather than hard-coding into the compiler. Not only equality, almost all of tuple operations should be distributed over all elements (if all of them satisfy a certain set of constraints which is representable through generic shapes).

e.g. using over a tuple full of IDisposables or await over a tuple full of elements for which the compiler can resolve GetAwaiter()?

In those cases it might make sense, but I think it might not always be so obvious how an operation should be distributed, if it should be at all. For the most appropriate behavior it might be necessary for the compiler to special-case the operations in question.

Contributor

HaloFour commented Mar 8, 2017

@alrz

I wonder if this could be implemented on top of shapes rather than hard-coding into the compiler. Not only equality, almost all of tuple operations should be distributed over all elements (if all of them satisfy a certain set of constraints which is representable through generic shapes).

e.g. using over a tuple full of IDisposables or await over a tuple full of elements for which the compiler can resolve GetAwaiter()?

In those cases it might make sense, but I think it might not always be so obvious how an operation should be distributed, if it should be at all. For the most appropriate behavior it might be necessary for the compiler to special-case the operations in question.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 8, 2017

Contributor

I think it might not always be so obvious how an operation should be distributed, if it should be at all. For the most appropriate behavior it might be necessary for the compiler to special-case the operations in question.

That directly depends on the shape implementation. I'm not sure why "special-casing" would make that more "appropriate". I think this would be the most sensible starting point for designing shapes. Once it's possible to model various operations through shapes there is no reason to special-case such features. IMO it'd be worth it to invest on the general solution rather than special-casing all these into the compiler.

Contributor

alrz commented Mar 8, 2017

I think it might not always be so obvious how an operation should be distributed, if it should be at all. For the most appropriate behavior it might be necessary for the compiler to special-case the operations in question.

That directly depends on the shape implementation. I'm not sure why "special-casing" would make that more "appropriate". I think this would be the most sensible starting point for designing shapes. Once it's possible to model various operations through shapes there is no reason to special-case such features. IMO it'd be worth it to invest on the general solution rather than special-casing all these into the compiler.

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Jun 5, 2017

@alrz

Once it's possible to model various operations through shapes there is no reason to special-case such features

I would normally agree with you, except I do think that it might be appropriate in the case of tuples. Because they should really be such a basic and integral feature I'm all in favour of the compiler 'inlining' the body of the '==' method for tuples, even if the method exists (at least notionally) as the operator '==' on a ValueTuple where T1, T2 etc are members of an Eq typeclass.

Richiban commented Jun 5, 2017

@alrz

Once it's possible to model various operations through shapes there is no reason to special-case such features

I would normally agree with you, except I do think that it might be appropriate in the case of tuples. Because they should really be such a basic and integral feature I'm all in favour of the compiler 'inlining' the body of the '==' method for tuples, even if the method exists (at least notionally) as the operator '==' on a ValueTuple where T1, T2 etc are members of an Eq typeclass.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Jun 5, 2017

Contributor

The case for inlininig was discussed at length in other threads before and concluded that roslyn is not the place for these optimizations (even though there were some good arguments against it, for instance, dotnet/roslyn#15644). In fact, that was my first concern regarding how shapes are implemented (#164) but as others have said, invocations of shape methods get specialized by the JIT.

Contributor

alrz commented Jun 5, 2017

The case for inlininig was discussed at length in other threads before and concluded that roslyn is not the place for these optimizations (even though there were some good arguments against it, for instance, dotnet/roslyn#15644). In fact, that was my first concern regarding how shapes are implemented (#164) but as others have said, invocations of shape methods get specialized by the JIT.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Feb 20, 2018

Contributor

How is this feature different from tuple patterns?

(1, 2, 3) is (1, 2, 3)
(1, 2, 3) == (1, 2, 3)

I think it's not, other than that you could compare variables with ==, right?

Contributor

alrz commented Feb 20, 2018

How is this feature different from tuple patterns?

(1, 2, 3) is (1, 2, 3)
(1, 2, 3) == (1, 2, 3)

I think it's not, other than that you could compare variables with ==, right?

@jcouv

This comment has been minimized.

Show comment
Hide comment
@jcouv

jcouv Feb 20, 2018

Member

@alrz This feature allows comparing two tuple variables (tuple1 == tuple2) as well, whereas positional patterns require constants.

Member

jcouv commented Feb 20, 2018

@alrz This feature allows comparing two tuple variables (tuple1 == tuple2) as well, whereas positional patterns require constants.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 21, 2018

@jcouv,

That doesn't sound right to me. Surely positional patterns for tuples will support recursive patterns, not just constants?

tuple is (var x, (1, var y),  3)

@alrz,
Surely "I think it's not, other than that you could compare variables with ==, right?" applies to all basic patterns, doesn't it?

x is 1
x == 1

DavidArno commented Feb 21, 2018

@jcouv,

That doesn't sound right to me. Surely positional patterns for tuples will support recursive patterns, not just constants?

tuple is (var x, (1, var y),  3)

@alrz,
Surely "I think it's not, other than that you could compare variables with ==, right?" applies to all basic patterns, doesn't it?

x is 1
x == 1
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 21, 2018

Contributor

@DavidArno

Yes, but the only actual values that you can compare must be themselves constants. So if you're comparing to other variables via pattern matching you'd have to use pattern variables and guards:

public void Foo((int, int) x, (int, int) y) {
    switch (x) {
        case (var a, var b) when a == y.Item1 && b == y.Item2: ...
    }
    // vs
    if (x == y) { ... }
}

I assume that you could also use it as shorthand for comparing equality of multiple items:

public void Foo(int a, int b, int c, int d, int e, int f) {
    if (a == d && b == e && c == f) { ... }
    // vs
    if ((a, b, c) == (d, e, f)) { ... }
}
Contributor

HaloFour commented Feb 21, 2018

@DavidArno

Yes, but the only actual values that you can compare must be themselves constants. So if you're comparing to other variables via pattern matching you'd have to use pattern variables and guards:

public void Foo((int, int) x, (int, int) y) {
    switch (x) {
        case (var a, var b) when a == y.Item1 && b == y.Item2: ...
    }
    // vs
    if (x == y) { ... }
}

I assume that you could also use it as shorthand for comparing equality of multiple items:

public void Foo(int a, int b, int c, int d, int e, int f) {
    if (a == d && b == e && c == f) { ... }
    // vs
    if ((a, b, c) == (d, e, f)) { ... }
}
@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 21, 2018

@HaloFour

I absolutely love the idea of:

public void Foo(int a, int b, int c, int d, int e, int f) {
    if ((a, b, c) == (d, e, f)) { ... }
}

Hopefully, @jcouv can work the same magic as he did for the immodestly named Arno Assignment Pattern so that the tuples get optimised away here too.

DavidArno commented Feb 21, 2018

@HaloFour

I absolutely love the idea of:

public void Foo(int a, int b, int c, int d, int e, int f) {
    if ((a, b, c) == (d, e, f)) { ... }
}

Hopefully, @jcouv can work the same magic as he did for the immodestly named Arno Assignment Pattern so that the tuples get optimised away here too.

@jcouv

This comment has been minimized.

Show comment
Hide comment
@jcouv

jcouv Feb 21, 2018

Member

@DavidArno (a, b, c) == (d, e, f) will likely require referencing the ValueTuple types for the compilation to succeed, but no ValueTuple reference will be emitted in IL.

Member

jcouv commented Feb 21, 2018

@DavidArno (a, b, c) == (d, e, f) will likely require referencing the ValueTuple types for the compilation to succeed, but no ValueTuple reference will be emitted in IL.

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Feb 21, 2018

Contributor

@jcouv Couldn't the ValueTuple requirement be fixed the same way it was for deconstruction?

Contributor

jnm2 commented Feb 21, 2018

@jcouv Couldn't the ValueTuple requirement be fixed the same way it was for deconstruction?

@jcouv

This comment has been minimized.

Show comment
Hide comment
@jcouv

jcouv Feb 22, 2018

Member

@jnm2 It's not obvious. Still looking into it ;-)

Member

jcouv commented Feb 22, 2018

@jnm2 It's not obvious. Still looking into it ;-)

@jcouv jcouv changed the title from Champion "Support for == and != on tuple types" to Champion "Support for == and != on tuple types" (15.7) Mar 16, 2018

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