Should tuple equality operators work on tuples with different types?

Here is the motivation for the pitch Deprecating Tuple Shuffles (Round 2) (which I agree with):

Even if tuple shuffles are deprecated, the following will still be allowed:

var a = (apples: 1, oranges: 2)
var b = (oranges: 1, apples: 2)
print(type(of: a) == type(of: b)) // false
print(a == b) // true (but imo this should not compile)

I think the standard library should not allow checking equality between tuples of different types, at least not without an explicitly expressed intent, like:

var a = (apples: 1, oranges: 2)
var b = (oranges: 1, apples: 2)
print(type(of: a) == type(of: b)) // false
print(a as (Int, Int) == b as (Int, Int)) // true (ok, fine since I seem to know what I'm doing here.)

  • Does anyone agree?

  • Is it possible to write equality operators for tuples in a way that requires lhs and rhs to be of the same type? (AFAICS it is not.) If not, is that problem?

1 Like

Can someone please give me a brief explanation to this question: Do we want (in the abstract, regardless of current implementation behavior) of member order and labels to matter?

I don't think so, because the operators are written to take unlabeled tuples, and we allow conversions that introduce and eliminate labels in argument position.

Perhaps a better design would have been to make these conversions explicit, eg, (x: 3, y: "hi").unlabeled == (3, 4), and (x: Int, y: String)(3, "hi") == (x: 3, 4: "hi").

3 Likes

Thanks,

But the problem as I see it is that they do take both unlabeled and labeled tuples, and mixes them up, and it's impossible to write them in any other (possibly preferred) way. Current Swift forces them to erase the label-part of tuple types and thereby accepting different types.

It is possible to write functions that does not erase any type info from tuple types:

func f<T>(_ a: T, _ b: T) {
    print("The type of \(a) and \(b) is \(T.self)")
}

func test() {
    let a = (apples: 1, oranges: 2)
    let b = (oranges: 1, apples: 2)
    f(a, b) // ERROR
    let c = (apples: 3, oranges: 4)
    f(a, c) // OK
}
test()

Here, the type parameter T preserves the true type of the arguments, so it will only accept two values of the same type. But the moment we try to constrain the function to work only for tuple types (instead of any type T) in order to access and eg compare the elements, we will unfortunately also loose type info (the label-part), and the function will start accepting tuples of different types (having different labels).


I'll try to reformulate the main question(s) of this thread:

It seems to me like the tuple equality operators of the standard library are inconsistent with the language in that they define equality for types that are different, thus allowing/encouraging comparing apples to oranges:

var a = (apples: 1, oranges: 2)
var b = (oranges: 1, apples: 2)
print(type(of: a) == type(of: b)) // false
print(a == b) // true (hmm)
  • I think (a == b) == true is nonsensical, because a and b are of different types and a.apples != b.apples and a.oranges != b.oranges.

  • Furthermore, it is impossible (in current Swift) to improve the tuple equality operators, ie rewrite them so that they'll work only for tuples of the same type.

Am I expressing valid concerns in the above two points?
If not, why not (why not also define equality between UInt8 values and Int values etc)?
If so, are there any reasonable solutions?

2 Likes

I think your concern is reasonable. Integer types do not have implicit conversions, and if they did, you would probably find similar problems there. I don't think there is any way to address this with today's Swift.

I really wish that in addition to getting rid of tuple shuffles we also got rid of the implicit conversions to add or remove labels. Those should have been explicit, if they were needed at all.

4 Likes

Do you mean that it's too late to get rid of the implicit conversions?

Or could it be done via a follow-up proposal to deprecating tuple shuffles (round 2)?

Doesn't that exist already? The following compiles (and runs) without problems:

let a: UInt8 = 1
let b: Int = 2
print(a == b)
2 Likes

Oh, right, there's a:

public static func == <Other>(lhs: UInt8, rhs: Other) -> Bool where Other : BinaryInteger

on each integer type (this one is for UInt8) ... which makes more sense than equality between different tuple types now that I think about it, bad example, I should have left that parentheses out.

I guess I extrapolated the fact that Swift won't implicitly convert between different numeric types to allow eg + between UInt8 and Int values.

1 Like

It's certainly possible, since there's no ABI impact and it could be staged in with a -swift-version check. However I don't know how keen the core team will be to take on more source breaks at this stage of the project.

Having said that, I personally think removing both tuple shuffles and the label conversions would be the way to go, I just wish we had thought of that in the Swift 3 days.

3 Likes