Skip to content

Instantly share code, notes, and snippets.

@seanwoodward
Last active March 19, 2024 04:36
Show Gist options
  • Save seanwoodward/44e2f5aca0dda994e9595eee58750f65 to your computer and use it in GitHub Desktop.
Save seanwoodward/44e2f5aca0dda994e9595eee58750f65 to your computer and use it in GitHub Desktop.
Tuple struct using Parameter Packs to facilitate things like using a tuple as keys in dictionaries or values in sets and heaps.
#if swift(>=5.9)
import Foundation
public struct Tuple<T, each U> {
private let head: T
private let tail: (repeat (each U))
public var values: (T, repeat each U) {
(head, repeat each tail)
}
public init(_ head: T, _ tail: repeat each U) {
self.head = head
self.tail = (repeat (each tail))
}
}
extension Tuple: Sendable where T: Sendable, repeat (each U): Sendable {}
extension Tuple: Hashable where T: Hashable, repeat (each U): Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.head)
repeat (hasher.combine(each self.tail))
}
}
extension Tuple: Equatable where T: Equatable, repeat (each U): Equatable {
private struct NotEqual: Error {}
public static func == (lhs: Tuple<T, repeat (each U)>, rhs: Tuple<T, repeat (each U)>) -> Bool {
guard lhs.head == rhs.head else { return false }
func isEqual<V: Equatable>(_ left: V, _ right: V) throws {
guard left == right else { throw NotEqual() }
return
}
do {
repeat try isEqual(each lhs.tail, each rhs.tail)
} catch {
return false
}
return true
}
}
extension Tuple: Comparable where T: Comparable, repeat (each U): Comparable {
// https://github.com/apple/swift-evolution/blob/1b0b339bc3072a83b5a6a529ae405a0f076c7d5d/proposals/0015-tuple-comparison-operators.md
private struct NotOrderBefore: Error { }
// throwing short circuits the checks
public static func < (lhs: Tuple<T, repeat (each U)>, rhs: Tuple<T, repeat (each U)>) -> Bool {
func isOrderedBeforeThrows<V: Comparable>(_ left: V, _ right: V) throws {
if left != right {
guard left < right else { throw NotOrderBefore() }
}
}
do {
try isOrderedBeforeThrows(lhs.head, rhs.head)
repeat try isOrderedBeforeThrows(each lhs.tail, each rhs.tail)
} catch {
return false
}
return true
}
// exhaustive
// public static func < (lhs: Tuple<T, repeat (each U)>, rhs: Tuple<T, repeat (each U)>) -> Bool {
// var results: [(notEqual: Bool, orderedBefore: Bool)] = [ (lhs.head != rhs.head, lhs.head < rhs.head)]
//
// func isOrderedBefore<V: Comparable>(_ left: V, _ right: V) {
// results.append((left != right, left < right))
// }
//
// repeat isOrderedBefore(each lhs.tail, each rhs.tail)
//
// return results.first(where: { $0.notEqual})?.orderedBefore ?? results.last!.orderedBefore
// }
}
extension Tuple: CustomStringConvertible {
public var description: String {
func describe<V>(_ value: V) {
if let _ = value as? any StringProtocol {
let valueString = String(describing: value)
// allows the presentation of a string value with quotes
// e.g head = "red", the description will be "red" with quotes
// not just, red, without quotes.
descriptions.append("\(valueString.debugDescription)")
} else {
descriptions.append("\(String(describing: value))")
}
}
var descriptions: [String] = []
describe(self.head)
repeat (describe(each tail))
return descriptions.count == 1 ? "\(descriptions[0])" : "(\(descriptions.joined(separator: ", ")))"
}
}
extension Tuple: CustomDebugStringConvertible {
public var debugDescription: String {
"values = \(self.description)"
}
}
#endif
@randomeizer
Copy link

I haven't really looked into exactly what they allow, to be honest. Can it support an arbitrary number of mixed types in the signature, like "real" tuples? Or is U constrained to a single type?

@seanwoodward
Copy link
Author

@randomeizer, yes parameter packs allow for arbitrary mixed types like built-in tuples. U types are not constrained, but they are not dynamic. You could not change an instance of one Tuple(1 as Int, 2.0 as Double) to an instance of Tuple("John", 490 as Int).

@randomeizer
Copy link

Neat. I need to dig into them a bit I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment