New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal for tuple comparison operators #45
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Tuple comparison operators | ||
|
||
* Proposal: TBD | ||
* Author(s): [Kevin Ballard](https://github.com/kballard) | ||
* Status: **Review** | ||
* Review manager: TBD | ||
|
||
## Introduction | ||
|
||
Implement comparison operators on tuples up to some arity. | ||
|
||
## Motivation | ||
|
||
It's annoying to try and compare tuples of comparable values and discover that | ||
tuples don't support any of the common comparison operators. There's an | ||
extremely obvious definition of `==` and `!=` for tuples of equatable values, | ||
and a reasonably obvious definition of the ordered comparison operators as well | ||
(lexicographical compare). | ||
|
||
Beyond just comparing tuples, being able to compare tuples also makes it easier | ||
to implement comparison operators for tuple-like structs, as the relevant | ||
operator can just compare tuples containing the struct properties. | ||
|
||
## Proposed solution | ||
|
||
The Swift standard library should provide generic implementations of the | ||
comparison operators for all tuples up to some specific arity. The arity should | ||
be chosen so as to balance convenience (all tuples support this) and code size | ||
(every definition adds to the size of the standard library). | ||
|
||
When Swift gains support for conditional conformation to protocols, and if Swift | ||
ever gains support for extending tuples, then the tuples up to the chosen arity | ||
should also be conditionally declared as conforming to `Equatable` and | ||
`Comparable`. | ||
|
||
If Swift ever gains support for variadic type parameters, then we should | ||
investigate redefining the operators (and protocol conformance) in terms of | ||
variadic types, assuming there's no serious codesize issues. | ||
|
||
## Detailed design | ||
|
||
The actual definitions will be generated by gyb. The proposed arity here is 6, | ||
which is large enough for most reasonable tuples (but not as large as I'd | ||
prefer), without having massive code increase. After implementing this proposal | ||
for arity 6, a Ninja-ReleaseAssert build increases codesize for | ||
`libswiftCore.dylib` (for both macosx and iphoneos) by 43.6KiB, which is a | ||
1.4% increase. | ||
|
||
The generated definitions look like the following (for arity 3): | ||
|
||
```swift | ||
@warn_unused_result | ||
public func == <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { | ||
return lhs.0 == rhs.0 && lhs.1 == rhs.1 && lhs.2 == rhs.2 | ||
} | ||
|
||
@warn_unused_result | ||
public func != <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { | ||
return lhs.0 != rhs.0 || lhs.1 != rhs.1 || lhs.2 != rhs.2 | ||
} | ||
|
||
@warn_unused_result | ||
public func < <A: Comparable, B: Comparable, C: Comparable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { | ||
if lhs.0 != rhs.0 { return lhs.0 < rhs.0 } | ||
if lhs.1 != rhs.1 { return lhs.1 < rhs.1 } | ||
return lhs.2 < rhs.2 | ||
} | ||
@warn_unused_result | ||
public func <= <A: Comparable, B: Comparable, C: Comparable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { | ||
if lhs.0 != rhs.0 { return lhs.0 < rhs.0 } | ||
if lhs.1 != rhs.1 { return lhs.1 < rhs.1 } | ||
return lhs.2 <= rhs.2 | ||
} | ||
@warn_unused_result | ||
public func > <A: Comparable, B: Comparable, C: Comparable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { | ||
if lhs.0 != rhs.0 { return lhs.0 > rhs.0 } | ||
if lhs.1 != rhs.1 { return lhs.1 > rhs.1 } | ||
return lhs.2 > rhs.2 | ||
} | ||
@warn_unused_result | ||
public func >= <A: Comparable, B: Comparable, C: Comparable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool { | ||
if lhs.0 != rhs.0 { return lhs.0 > rhs.0 } | ||
if lhs.1 != rhs.1 { return lhs.1 > rhs.1 } | ||
return lhs.2 >= rhs.2 | ||
} | ||
``` | ||
|
||
## Impact on existing code | ||
|
||
No existing code should be affected. | ||
|
||
## Alternatives considered | ||
|
||
I tested building a Ninja-ReleaseAssert build for tuples up to arity 12, but | ||
that had a 171KiB codesize increase (5.5%). I have not tried any other arities. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reads from left-to-right? Like how version numbers work? But doesn’t that tie to a western way of reading?
<
has subtleties that==
doesn’t I think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it reads from first-to-last. Even if you write in an RTL language,
(Int, Double)
still has.0
be theInt
and.1
be theDouble
. And this even matches String behavior; the first character in a String is still the first character in a String regardless of whether the character belongs to an LTR or RTL language; the RTL nature of the language only affects how it's displayed on screen. Presumably if you used a Tuple to represent some data that gets displayed on screen, in an RTL locale you'd display the.0
element on the right and the.1
element on the left.