How to Extend Tuples in Swift

Can I extend Tuples in Swift?

You cannot extend tuple types in Swift.
According to
Types, there are named types (which
can be extended) and compound types. Tuples and functions are compound
types.

See also (emphasis added):

Extensions
Extensions add new functionality to an existing
class, structure, or enumeration type.

Extending Array of tuples of equatables to find the first tuple matching

You can achieve that by constraining the method instead of the
extension:

extension Array  {
func contains<E1, E2>(_ tuple: (E1, E2)) -> Bool where E1: Equatable, E2: Equatable, Element == (E1, E2) {
return contains { $0.0 == tuple.0 && $0.1 == tuple.1 }
}
}

Example:

let a = [(1, "a"), (2, "b")]
print(a.contains((1, "a")))

Swift - Joining two tuple arrays based on their values

let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]

let t2Dict = tuple2.reduce(into: [String:String]()) { (dict, args) in
let (date, index) = args
dict[index] = date
}

let tuple3 = tuple1.compactMap { args -> (String, String)? in
let (score, index) = args
guard let date = t2Dict[index] else { return nil }
return (score, date)
}

It's not as pretty as the others, but it's far more efficient to collapse one of the tuples into a dictionary first.

Why Swift Tuples can be compared only when the number of elements is less than or equal to 6?

Background: Tuples aren't Equatable

Swift's tuples aren't Equatable, and they actually can't be (for now). It's impossible to write something like:

extension (T1, T2): Equatable { // Invalid
// ...
}

This is because Swift's tuples are structural types: Their identity is derived from their structure. Your (Int, String) is the same as my (Int, String).

You can contrast this from nominal types, whose identity is solely based off their name (well, and the name of the module that defines them), and whose structure is irrelevant. An enum E1 { case a, b } is different from an enum E2 { case a, b }, despite being structurally equivalent.

In Swift, only nominal types can conform to protocols (like Equatble), which precludes tuples from being able to participate.

...but == operators exist

Despite this, == operators for comparing tuples are provided by the standard library. (But remember, since there is still no conformance to Equatable, you can't pass a tuple to a function where an Equatable type is expected, e.g. func f<T: Equatable>(input: T).)

One == operator has to be manually be defined for every tuple arity, like:

public func == <A: Equatable, B: Equatable,                                                       >(lhs: (A,B        ), rhs: (A,B        )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, >(lhs: (A,B,C ), rhs: (A,B,C )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, >(lhs: (A,B,C,D ), rhs: (A,B,C,D )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, >(lhs: (A,B,C,D,E ), rhs: (A,B,C,D,E )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool { ... }

Of course, this would be really tedious to write-out by hand. Instead, it's written using GYB ("Generate your Boilerplate"), a light-weight Python templating tool. It allows the library authors to implement == using just:

% for arity in range(2,7):
% typeParams = [chr(ord("A") + i) for i in range(arity)]
% tupleT = "({})".format(",".join(typeParams))
% equatableTypeParams = ", ".join(["{}: Equatable".format(c) for c in typeParams])

// ...

@inlinable // trivial-implementation
public func == <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
${", ".join("lhs.{}".format(i) for i in range(1, arity))}
) == (
${", ".join("rhs.{}".format(i) for i in range(1, arity))}
)
}

Which then gets expanded out by GYB to:

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable>(lhs: (A,B), rhs: (A,B)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1
) == (
rhs.1
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2
) == (
rhs.1, rhs.2
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable>(lhs: (A,B,C,D), rhs: (A,B,C,D)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3
) == (
rhs.1, rhs.2, rhs.3
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable>(lhs: (A,B,C,D,E), rhs: (A,B,C,D,E)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4
) == (
rhs.1, rhs.2, rhs.3, rhs.4
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4, lhs.5
) == (
rhs.1, rhs.2, rhs.3, rhs.4, rhs.5
)
}

Even though they automated this boilerplate and could theoretically change for arity in range(2,7): to for arity in range(2,999):, there is still a cost: All of these implementations have to be compiled and produce machine code that ends up bloating the standard library. Thus, there's still a need for a cutoff. The library authors chose 6, though I don't know how they settled on that number in particular.

Future

There's two ways this might improve in the future:

  1. There is a Swift Evolution pitch (not yet implemented, so there's no official proposal yet) to introduce Variadic generics, which explicitly mentions this as one of the motivating examples:

    Finally, tuples have always held a special place in the Swift language, but working with arbitrary tuples remains a challenge today. In particular, there is no way to extend tuples, and so clients like the Swift Standard Library must take a similarly boilerplate-heavy approach and define special overloads at each arity for the comparison operators. There, the Standard Library chooses to artificially limit its overload set to tuples of length between 2 and 7, with each additional overload placing ever more strain on the type checker. Of particular note: This proposal lays the ground work for non-nominal conformances, but syntax for such conformances are out of scope.

    This proposed language feature would allow one to write:

    public func == <T...>(lhs: T..., rhs: T...) where T: Equatable -> Bool { 
    for (l, r) in zip(lhs, rhs) {
    guard l == r else { return false }
    }
    return true
    }

    Which would be a general-purpose == operator that can handle tuples or any arity.

  2. There is also interest in potentially supporting non-nominal conformances, allowing structural types like Tuples to conform to protocols (like Equatable).

    That would allow one to something like:

    extension<T...> (T...): Equatable where T: Equatable {
    public static func == (lhs: Self, rhs: Self) -> Bool {
    for (l, r) in zip(lhs, rhs) {
    guard l == r else { return false }
    }
    return true
    }
    }

merging two arrays of tuples

You can use a single map to achieve your calls. You call map on weightCalculated, which you know for sure will contain an entry for each day, then check if weightsMeasured contains an entry for the same day or not. If there is a measurement, return the measurement, otherwise return the calculated value.

let mergedWeights = weightsCalculated.map({ todayCalculated->PointTuple in
if let todayMeasured = weightsMeasured.first(where: { $0.day == todayCalculated.day}) {
return todayMeasured
} else {
return todayCalculated
}
})

Or you can even write this as a one liner using the nil-coalescing operator to return the measured value if it is found and the calculated value otherwise:

let mergedWeights = weightsCalculated.map({ todayCalculated in return weightsMeasured.first(where: { $0.day == todayCalculated.day}) ?? todayCalculated })

When trying out the above code bear in mind that you had a typo in the variable name weightCalculated, which I corrected in my code.

List of tuples with different size and type in swift

This looks like a job for a struct.

Since there are 5 different permutations of your data, you can create 5 structs, each of which to conform to a common protocol. You can then make your array hold a list of those structs by their more general protocol type.

Tuple-Array with variable content types & length?

A tuple is inappropriate because you cannot declare a tuple in a generic manner.

A possible solution is to declare data as an array of CustomStringConvertible instead of a tuple.

The benefit is you can pass any type which supports String Interpolation and the number of items in the array is variable.

func debugData(names: [String], data: [[CustomStringConvertible]] ) {
var debugLine = ""
for line in data {
for i in 0..<line.count {
debugLine += "\(names[i])=\(line[i]) "
}
print(debugLine)
debugLine = ""
}
}

debugData(names: ["Name", "Age", "SexM"], data: [["Alex", 5, true], ["Lisa", 7, false], ["Max", 9, true]])


Related Topics



Leave a reply



Submit