i think in general we do not use Comparable
as much as C++ uses their equivalent of that concept. which is great because Comparable
is easily my least-favorite conformance to implement by hand. but sometimes it is unavoidable, because you need it for things like deterministic serialization.
which sucks because anything that has optional stored properties is really hard to make Comparable
, because Optional<T>
doesn’t have a conditional conformance to Comparable
. (Array<T>
has a similar problem.)
here’s an example. this type models the generic signature of a swift extension:
extension Extension
{
@frozen public
struct Signature:Equatable, Hashable, Sendable
{
/// The type extended by the relevant extension group.
public
let type:ScalarSymbolResolution
/// The generic constraints of the relevant extension group.
/// An empty array indicates an unconstrained extension, while
/// nil indicates an unknown generic context.
public
let conditions:[GenericConstraint<ScalarSymbolResolution>]?
}
}
(the conditions
field is optional because SymbolGraphGen emits the wrong generic signatures for members inherited through protocol conformances. but SymbolGraphGen has a lot of bugs and it doesn’t seem like Apple considers it important, so i guess we just have to accept it is broken and find a workaround.)
conceptually, the <
implementation should just be:
extension Extension.Signature:Comparable
{
public static
func < (lhs:Self, rhs:Self) -> Bool
{
(lhs.type, lhs.conditions) < (rhs.type, rhs.conditions)
}
}
but that doesn’t work because neither Array
nor Optional
is conditionally Comparable
. so instead we need this monstrosity:
extension Extension.Signature:Comparable
{
public static
func < (lhs:Self, rhs:Self) -> Bool
{
if lhs.type < rhs.type
{
return true
}
else if lhs.type > rhs.type
{
return false
}
switch (lhs.conditions, rhs.conditions)
{
case (_, nil):
return false
case (nil, _?):
return true
case (let lhs?, let rhs?):
return lhs.lexicographicallyPrecedes(rhs)
}
}
}
i say that this is hard and not just tedious because there is a lot of control flow to think about here, when the rule is conceptually just:
-
self.type
has the highest sort precedence -
nil
self.conditions
sort before non-nil
conditions.
in another life, i thought Optional
should not be Comparable
, because maybe you want nil
to sort after non-nil
. so i left Optional
out of SE-266. but now i am thinking we may have made the common use case disproportionally painful to avoid infringing on a less-common use case.
i can’t think of any reason why Array
should not be Comparable
, other than a really weak argument about new users falling into performance traps with expensive <
’s. (but this trap already exists with String
comparisons.)
so i propose:
-
we make
Optional<T>
conditionallyComparable
, withnil
sorting before everything else, which matches lexicographical intuition. -
we make
Array<T>
conditionallyComparable
, in recognition of the fact that sometimes we really do need to do a very slow array comparison.
thoughts?