Last active
March 19, 2024 04:36
-
-
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.
This file contains hidden or 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
#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, 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)
.
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
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?