[swift-evolution] Proposal: Implement == and < for tuples where possible, up to some high arity
plx
plxswift at icloud.com
Thu Dec 10 12:00:07 CST 2015
If these are to be added to the standard library it seems prudent to put them in under their own names — e.g. `#<`, and then `#==` for consistency — reserving `<` for any user-defined logic.
The reason I suggest this is that at least at present, the existing method-dispatch logic seems likely to lead to unintended consequences if e.g. `<` is defined so broadly; if you can paste the below into a playground you can see the kind of non-intuitive behavior that can crop up here:
// proposed function, arity-2
func == <A:Equatable,B:Equatable> (lhs: (A,B), rhs: (A,B)) -> Bool {
return lhs.0 == rhs.0 && lhs.1 == rhs.1
}
// proposed function, arity-2
func < <A:Comparable,B:Comparable> (lhs: (A,B), rhs: (A,B)) -> Bool {
if lhs.0 != rhs.0 { return lhs.0 < rhs.0 }
return lhs.1 < rhs.1
}
// concrete `<` implementation:
func < (lhs: (String,String), rhs: (String,String)) -> Bool {
switch customComparison(lhs.0, rhs.0) {
case .OrderedAscending: return true
case .OrderedDescending: return false
case .OrderedSame:
return customComparison(lhs.1, rhs.1) == .OrderedAscending
}
}
// helper for `<`
private func customComparison(lhs: String, _ rhs: String) -> NSComparisonResult {
return (lhs as NSString).compare(rhs, options: [.CaseInsensitiveSearch, .NumericSearch])
}
// trouble begins here:
extension SequenceType {
func extractOrderedPairs<K:Comparable,Q:Comparable>(extractor: (Self.Generator.Element) -> (K,Q)) -> [(K,Q)] {
return self.map(extractor).sort() {
(l:(K,Q),r:(K,Q)) -> Bool
in
return l < r
}
}
func extractOrderedPairs<K,Q>(
isOrderedBefore: ((K,Q),(K,Q)) -> Bool,
_ extractor: (Self.Generator.Element) -> (K,Q)) -> [(K,Q)] {
return self.map(extractor).sort(isOrderedBefore)
}
}
// helper for below:
extension String {
var fileNamePieces: (String,String) {
get {
let lastComponent = (self as NSString).lastPathComponent
return (
(lastComponent as NSString).stringByDeletingPathExtension,
(lastComponent as NSString).pathExtension
)
}
}
}
let someFileNames = ["Image.png", "Image.jpeg", "Image.bmp", "Essay1.txt", "Essay11.txt", "Essay2.txt"]
let pairsV1 = someFileNames.extractOrderedPairs() { $0.fileNamePieces }
let pairsV2 = someFileNames.extractOrderedPairs(<) { $0.fileNamePieces }
// ^ note the `<` that gets passed-in
var pairV1Mismatches = 0
for index in 0..<(pairsV1.count-1) {
if !(pairsV1[index] < pairsV1[index + 1]) {
print("found mismatch: \(pairsV1[index]) !< \(pairsV1[index + 1])!")
pairV1Mismatches += 1
}
}
pairV1Mismatches // 1
var pairV2Mismatches = 0
for index in 0..<(pairsV2.count-1) {
if !(pairsV2[index] < pairsV2[index + 1]) {
print("found mismatch: \(pairsV2[index]) !< \(pairsV2[index + 1])!")
pairV2Mismatches += 1
}
}
pairV2Mismatches // 0
// END SNIPPET
Yes this is very contrived — and hopefully not something someone would write intentionally! — and yes if you understand the various scopes and dispatch rules involved you can work out why this happens, but I don’t think it’s intuitive (and thus it’s easy to stumble upon accidentally). I also think that with additional layers of generics / protocol extensions / customized implementations / etc. involved there’ll be a few more flavors of non-intuitive outcomes one can possibly stumble into.
Whence the suggestion that if these are to go into the standard library, put them in under distinct names, rather than as e.g. `==` and `<`. (FWIW `==` is by itself less problematic, but the comparisons likely customization targets, hence my concerns).
> On Dec 10, 2015, at 1:14 AM, Kevin Ballard via swift-evolution <swift-evolution at swift.org> wrote:
>
> I've submitted this proposal as https://github.com/apple/swift-evolution/pull/45
>
> -Kevin Ballard
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list