SE-0283: Tuples conform to Equatable, Comparable, and Hashable

The review of SE-0283: Tuples Conform to Equatable, Comparable, and Hashable begins now and runs through May 14, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Saleem Abdulrasool
Review Manager

38 Likes

+1 can't wait to get EHC support with tuples

1 Like

Because these are special conformances being added before other language features allows us to create real conformances, there is a level of runtime support needed to enable these conformances to work properly. Going forward this means we'll need to keep the entry points needed for these to work even after tuples are able to properly conform to protocols.

Does this mean, if tuple gets "regular" conformance, these will be the default implementation?

No, it means the runtime functions will have to stick around “for ever”, even if/when general support for tuple conformances are added.

Huge +1. I have wanted this for a very long time and can't wait to see it land. It's a nice step along the path to eventual user-provided conformances for tuples that covers the most common use cases. It would be really great if this can make it into Swift 5.3.

2 Likes

Regarding the Comparable conformance, I am not sure if lexicographic ordering is something so semantically clear that it should be the defaulted to.

When declaring Comparable conformance for structs, it is a conscious decision to do so and one would need to precisely specify the comparison logic; on the other hand, if all possible tuples conform to Comparable automatically, this could lead to some unexpected semantics, especially when elements are of different types: e.g., there's no clear indication why (10, "Mary") is larger than (5, "Albert")— and I am not sure if the declaration order is what should impose the ordering. I suspect there is a lot of former code that hasn't been built with this semantics in mind, and this behaviour could be confusing.

I would suggest that either only homogenous tuples (that is, where all elements are of the same type) would conform to Comparable or that this wouldn't be enabled at all.

5 Likes

This is long-overdue and truly a wonderful thing to happen.

One issue not tackled in the proposal text--and I think I asked this also in the pitch phase--is what will come of the concrete functions we've already defined in the predecessor proposal, SE-0015, which will effectively become obsolete. I think we should have some discussion about those functions as part of this proposal; it could be as simple as deciding that "we won't touch that stuff," but I hope we will do a little more:

Obviously, we cannot outright remove those previously added functions because of ABI stability concerns. However, it is worth remembering that they once comprised 1.4% of the code size of libswiftCore.dylib!

If I write (42, 42) < (43, 43) in the next version of Swift, will it favor the existing concrete overloads or the synthesized versions that come with protocol conformance?

In my view, if possible, we would deprecate the concrete overloads in some way so that they are no longer called from new code. Ideally, too, their implementation would be changed so that they are simply wrappers that call the new, synthesized version.

I haven't thought in depth regarding the technicalities of how to make this happen, though.

It is a significant and highly useful addition to the language for reasons already discussed extensively. There cannot be a proposal that could fit more well with the feel and direction of Swift than this one, as it's literally discussed in a previously approved proposal (i.e., SE-0015). Indeed I have been hoping for this day since that prior proposal was accepted more than four (!) years ago; I can't pretend to have been studying the topic continuously for four years, but it's certainly been percolating.

1 Like

As discussed in the pitch phase, this is already the currently implemented behavior in Swift (that is, ((10, "Mary") > (5, "Albert")) == true right now). We have had comparison operators for tuples up to arity 6 since SE-0015, approved four years ago. For those types, the only addition here is the formal conformance to Comparable so that the same operations can be performed in a generic context.

11 Likes

It will favor the protocol conformance version over the concrete overload.

Because we will favor the protocol conformance, these concrete overloads will naturally deprecate themselves from new code. I haven't looked into changing their implementation to be wrappers, but that's something I would be willing to test if need be.

1 Like

This has been a sore spot in the language for a long time and I'm thrilled to see it being addressed. The presence of the relevant operators (==, <) makes the lack of conformance even more surprising and irritating every time I run into the issue. Although we lack the language features for general structural type conformance, this very much fits with the feel and direction of Swift. Almost every Swift developer I've spoken to about this already assumed that tuples were Equatable based on the presence of the == operator. I'm very excited to see this feature make it to review.

+1.

2 Likes

To be fair, Comparable in Swift really means "this is a type that has a defined total ordering." Ideally its operator would be ≺ (precedes; "\u{227a}"; \prec in TeX), not < (less than).

This being said, if you want your product type to be ordered in a different way, you can declare a struct and define its Comparable conformance.

That's what bothered me a little — just like one can have multiple norms over vector spaces, a total ordering is not some well-defined intrinsic property, even though some are considered to be more "standard" than others. But I wasn't aware that Swift already had this implemented; I still think that from a more puristic point of view, it is not very reasonable — but of course I have no say on this if it's already there.

1 Like

Generally, I support the proposal.

However, the proposal seems to underestimate and underplay the amount of technical debt that the implementation will entail. The technical debt will not be neatly isolated to a couple of new runtime entrypoints. Various runtime functionality will need to special case tuples and/or EHC protocols and call into those new entrypoints when appropriate. Once we have variadic generics, this fallback code in the runtime will become difficult to test because executing it would require convincing the compiler to generate old-style code.

Beyond the runtime, special casing code will be added to the type checker (to recognize that tuples conform to these three protocols, to pretend that we have relational operators with arbitrary arity etc.), to various generics-related code that checks protocol conformances, to SILGen to codegen those relational operators and protocol witness tables etc.

If in future, when we add variadic generics, if we decide to allow generating old-style code by setting an older deployment target, then some technical debt in the frontend will remain, however, it would be easier to test the technical debt in the runtime. If we clean up the technical debt in the frontend and only generate new-style code, it would be difficult to test fallback code paths in the runtime.

Yes, it is a significant problem, as witnessed by various user feedback and previous efforts in SE-0015.

Yes, this attempt at fixing the problem, even though it takes shortcuts in the implementation, from the user's point of view will look like the eventual implementation based on variadic generics.

std::tuple in C++ is equatable and comparable, but interestingly not hashable -- and that is a know pain point.

Tuples in Rust are equatable (PartialEq, Eq), comparable (PartialOrd, Ord), and hashable (Hash). However, Rust only implements these traits up to the arity of 12, and it is considered a temporary limitation.

I read the proposal, I didn't participate in the discussion.

1 Like

What would be the difference from the user's point of view? Both will behave identically.

These entry points are already baked into the ABI, so why not make use of them when appropriate? It is not like we would be able to remove them.

We could hide them from documentation though.

:+1: :+1: :+1:

Big +1 for me. I'd also like to see anonymous structs make it in to enhance Swift's facilities for lightweight types

Disclaimer: I’m not a compiler engineer. That said, aren’t these functions inlineable, meaning that their implantation was hoisted into the call site, and therefore any ABI concerns are void? If we remove them from the stdlib, they’ll still be part of previously compiled client binaries, non? Or am I misunderstanding this?

Not to derail the discussion, but what's the status of Variadic Generics? I remember quite some discussion on that some months ago, but haven't seen anything since. In my opinion it's one of the most obvious missing pieces of the language at the moment.

Inlinable does not mean always inlined. These functions weren't marked @_alwaysEmitIntoClient, although that would have been wise perhaps.

1 Like

Very positive.

Yes! Especially the lack of equatable and hashable conformances, creates quite a lot of headache for generic code, especially when generic over void.

I think so. I realize what others are saying about technical debt being added to the runtime and/or type checker. And while I don’t really have the skill to evaluate the actual limitations of the implementation, I certainly can sympathize with the desire to limit technical debt.

However, I can only attest to the utility of the feature as a user, and as such, I feel it really fits the direction of Swift very well. I trust that the core team can make the right decision based on cost/utility. But it would be a shame if it didn’t work out. I’ve been missing this feature for very very long.

Swift is my main programming language and whatever experience I have with e.g Rust is very limited.

I’ve followed and participated in several previous discussion threads, but haven’t reviewed the technical implementation.

1 Like
Terms of Service

Privacy Policy

Cookie Policy