Elevator Pitch: Although it's highly desired, allowing tuple types to conform to protocols is something we must not support.
Primary Reason: Conflicts
Secondary Reason: May go against what tuples are all about
If you think that slapping a protocol on a tuple is awesome, you're probably not the only one to come up with it. The problem comes from the fact that tuple type declarations are global (modulo that a scope can access all the components' types). What happens if you bring in a library that already had the protocol added to a tuple type you wanted to add? What happens if two different libraries do this? Would there be some kind of priority system? Since you can differentiate function overloads with protocol conformance, what if you didn't want a particular tuple type to have a conformance? There's no way to remove a conformance.
This can't even begin to work unless tuples' conformances can't go past internal
, and thus not visible to outside code. In other words, slapping an irrevocable conformance to a global public type you really shouldn't have control over is rude to anyone who disagrees with your decision.
The best workaround would be a newtype
facility and restrict your conformance to that type copy. And that's better from a philosophical view too. Tuples are supposed to be immediate; if your abstraction should have a richer interface, including protocol conformance, you are getting into struct
territory.
There's another practical problem, people want to do Equatable
and such on tuples. You can't just do a variadic generic function (once we support those) ==
that needs each field's type to be Equatable
, because we need to allow a tuple that has a field that is itself a tuple to be supported. (And if we add sum-tuples and fixed-size arrays as structural types, the combinations explode.) I think we need a family of global generic functions:
func areEqual<T: Equatable>(_ l: T, _ r: T) -> Bool? {
return l == r
}
func areEqual<T>(_ l: T, _ r: T) -> Bool? {
// Check if T is a function pointer; if so, compare.
// Check if T is already covered by an overload; if so, return that call.
return nil
}
func areEqual<variadic U>(_ l: (U...), _ r: (U...)) -> Bool? {
for lens in lenses(of: (U...).Type) {
guard let fieldEqual = areEqual(l[key: lens], r[key: lens]) else {
return nil
}
guard fieldEqual else { return false }
}
return true
}
// Theoretical sum-tuple
func areEqual<variadic U>(_ l: (; U... ;), _ r: (; U... ;)) -> Bool? {
for prism in prisms(of: (; U... ;).Type) {
switch (l[key: prism], r[key: prism]) {
case let (ll?, rr?):
return areEqual(ll, rr)
case (_?, nil), (nil, _?):
return false // Or should `nil` be returned here?
case (nil, nil):
continue
}
}
fatalError("One of the cases should have been reached!")
}
// Theoretical fixed-size array
func areEqual<T, variadic let N: Int>(_ l: [N... ; T], _ r: [N... ; T]) -> Bool? {
for i in indices(of: l) {
guard let elementEqual = areEqual(l[i], r[i]) else { return nil }
guard elementEqual else { return false }
}
return true
}