Do tuples conform to Comparable?

92 Views Asked by At

Tuples in Swift seem to conform to Comparable inasmuch as I get these results:

print ( (3,0) < (2,10000) ) // false
print ( (0,0,5) < (0,0,7) ) // true

It seems to do a left-right member-by-member comparison of the tuples: it compares the first tuple members against each other, then the 2nd, and so on, until it finds a pair that are nonequal, and returns whether the first member of that pair is less than the second. Very sensible.

It even works with tuples that contain multiple types, as long as they are comparable, and as long as the two tuples being compared are of the same type:

print( ("a",4) < ("b",3) ) // true

So why does this give an error?

print ( [(1,2),(0,2)].max() )   ⛔️ Type '(Int, Int)' cannot conform to 'Comparable'

Thinking that maybe it would help if I declared the types explicitly, I tried:

typealias IntPair = (Int,Int)

let a: IntPair = (0,1)
let b: IntPair = (0,2)
let array: [IntPair] = [a,b]
print(array.max())   ⛔️ Type '(Int, Int)' cannot conform to 'Comparable'

My only conclusion is that the static function < is defined on tuples of the same type, whose components are all Comparable, but those tuples nonetheless do not conform to Comparable. But that doesn't seem very likely.

Does anyone know why max() does not work on arrays of tuples of the same type whose arguments are all Comparable?

2

There are 2 best solutions below

1
Alexander On

Nope! Only nominal ("named") types can conform to protocols, which excludes tuples.

The standard library just happens to define < operators for tuples up to 6 elements.

0
Sweeper On

My only conclusion is that the static function < is defined on tuples of the same type, whose components are all Comparable, but those tuples nonetheless do not conform to Comparable.

This is exactly the case here.

The operators for tuples are declared in Tuple.swift.gyb. Here is an excerpt:

%   comparableTypeParams = ", ".join(["{}: Comparable".format(c) for c in typeParams])
%   for op, phrase in comparableOperators:
/// Returns a Boolean value indicating whether the first tuple is ordered
/// ${phrase} the second in a lexicographical ordering.
...
@inlinable // trivial-implementation
public func ${op} <${comparableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
  if lhs.0 != rhs.0 { return lhs.0 ${op} rhs.0 }
  /*tail*/ return (
    ${", ".join("lhs.{}".format(i) for i in range(1, arity))}
  ) ${op} (
    ${", ".join("rhs.{}".format(i) for i in range(1, arity))}
  )
}
%   end

It is not possible to conform tuples to Comparable because that would require the compiler to generate a protocol witness for the comparison operators This protocol witness is what would be called when you call a protocol requirement "through the protocol", e.g.

func f<T; Comparable>(x: T, y: T) {
    print(x < y) // here it would call the protocol witness of the "<" in the metadata of T
}

Alternatively, allow extensions on tuples and add a Comparable extension on tuples to the standard library. Either way, this would require changes to the compiler, and probably outweighs the benefit of having Comparable tuples.

If you want to use max on an array of tuples, you can just pass the operator to the by: parameter:

[(1, 2), (3, 4)].max(by: <)