SE-0393: Value and Type Parameter Packs mentions "Tuple conformances" in its "future direction" section. To not sidetrack that thread let me present a raw idea about tuples here.
Homogenious Tuples
Motivation
Currently it is not possible to declare homogeneous tuples (a tuple of the same typed elements) other than doing it explicitly:
var value = (0, 0, 0, 0, 0) // a tuple with 5 Ints
This becomes unpractical quickly:
// a tuple of 1234 Ints
var value = (0, 0, 0, /* ... 1230 elements skipped... */, 0)
Why large† homogeneous tuples are important:
- compared to Arrays: tuples are POD types with no memory allocation, locks or ARC overhead (there's no internal reference object sitting in them) which makes them highly appealing in certain contexts (e.g. realtime code).
- compared to structs: tuple layout is known and guaranteed (all elements packed back to back according to their stride). This is important when working with fixed memory layouts (e.g. treating raw memory as a tuple of n bytes).
- compared to UnsafePointers: tuples could be considered "safer".
† - we use "large" here in the same sense as what "large number of elements in a struct" is. For example we can have a tuple (or a struct) with 1000 elements, but going much further from it (say 10K) would be a stretch for all components involved (compiler, runtime, stack usage requirements). Similar to a limit on a number of elements in a struct, while it is impractical to hardcode a particular hard limit on the number of tuple elements (arity), as a guideline we could define it to be "a few thousand elements tops". Note that tuple size is roughly count * element_stride
, so the practical limit on the count should take this into account. This is very similar to the (unspoken but well appreciated) limit on struct elements: while you may have a struct with 1000 byte-sized fields and use for your "stack-allocated" variables, it would be unwise to have a struct of 1000 CATransform3D
fields (128 bytes each) as a stack allocated variable.
Beyond impracticality of its declaration today it's hard to work with large tuples as there is no subscript operation (other than "subscripting by a constant index") or getting tuple arity or its element type ††.
(†† we could use type(of: value.0)
if we have a value but no way of getting element type from a tuple type itself).
Note that very little is required from the language / standard library to have a complete homogeneous tuple support. Just this: ability to declare a large homogeneous tuple and specify its element type and count – everything else could be derived from this).
Here's a sketch example how we enhance this:
Base homogeneous tuple support
One possibility is to have homogeneous tuple definition looking like a generic type (almost):
typealias Foo = Tuple<Int, let count: 1234>
let foo: Foo = .init(repeating: 0, count: 1234)
let foo = Foo<Int, let count: 1234>(repeating: 0, count: 1234)
print(Foo.Element) // Int
This is basically it! Everything else (see below) could be derived.
Suggestions or alternatives on how to achieve this base functionality are greatly appreciated.
Derived homogeneous tuples functionality
Everything in this section could be derived from the above or similar base tuple support functionality.
Tuple arity
Foo.count
foo.count
Possible tuple arity implementation:
static var count: Int {
// last element is "Element.size" sized, all other elements are "Element.stride" sized.
1 + (MemoryLayout<Self>.size - MemoryLayout<Element>.size) / MemoryLayout<Element>.stride
}
var count: Int { Self.count }
Alternatively, if only "instance" count is needed we may do so without referencing "Element" type:
var count: Int {
1 + (MemoryLayout.size(ofValue: self) - MemoryLayout.size(ofValue: self.0) / MemoryLayout.stride(ofValue: self.0))
}
Tuple subscript
foo[42] // same as value.42
var i = 42
foo[i] = 24
Possible tuple subscript implementation:
extension Tuple {
subscript (_ index: Int) -> Element {
get {
precondition(index >= 0 && index < count)
var v = self
return withUnsafeBytes(of: &v) { p in
let r = p.baseAddress!.assumingMemoryBound(to: Element.self)
return r[index]
}
}
set {
precondition(index >= 0 && index < count)
withUnsafeMutableBytes(of: &self) { p in
let r = p.baseAddress!.assumingMemoryBound(to: Element.self)
r[index] = newValue
}
}
}
}
Tuple Hashable conformance
Possible tuple hashability implementation:
extension Tuple: Hashable where Element: Hashable {
func hash(into hasher: inout Hasher) {
for i in 0 ..< count {
hasher.combine(self[i])
}
}
}
Tuple Equatable conformance
Possible tuple equatability implementation:
extension Tuple: Equatable where Element: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
for i in 0 ..< lhs.count {
if lhs[i] != rhs[i] {
return false
}
}
return true
}
}