Dictionaries are a great place to focus for this. Because Slava's second tuple conversion type doesn't actually work with them directly, I use this:
/// Remove the labels from a tuple.
/// - Parameter tuple: A tuple that may have at least one label.
@inlinable public func removeLabels<T0, T1>(_ tuple: (T0, T1)) -> (T0, T1) {
tuple
}
That allows for writing missing overloads, e.g.:
extension Dictionary: DictionaryProtocol { }
extension OrderedDictionary: DictionaryProtocol { }
public extension DictionaryProtocol {
/// Creates a new dictionary from the key-value pairs in the given sequence.
///
/// - Parameter keysAndValues: A sequence of key-value pairs to use for
/// the new dictionary. Every key in `keysAndValues` must be unique.
/// - Returns: A new dictionary initialized with the elements of `keysAndValues`.
/// - Precondition: The sequence must not have duplicate keys.
/// - Note: Differs from the initializer in the standard library, which doesn't allow labeled tuple elements.
/// This can't support *all* labels, but it does support `(key:value:)` specifically,
/// which `Dictionary` and `KeyValuePairs` use for their elements.
@inlinable init(uniqueKeysWithValues keysAndValues: some Sequence<Element>) {
self.init(uniqueKeysWithValues: keysAndValues.lazy.map(removeLabels))
}
/// `merge`, with labeled tuples.
///
/// - Parameter pairs: Either `KeyValuePairs<Key, Value.Element>`
/// or a `Sequence` with the same element type as that.
@inlinable mutating func merge(
_ pairs: some Sequence<Element>,
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows {
try merge(pairs.lazy.map(removeLabels), uniquingKeysWith: combine)
}
Relevant bits of `DictionaryProtocol`
public protocol DictionaryProtocol<Key, Value>: Sequence where Element == (key: Key, value: Value) {
associatedtype Key
associatedtype Value
init(uniqueKeysWithValues: some Sequence<(Key, Value)>)
mutating func merge(
_: some Sequence<(Key, Value)>,
uniquingKeysWith: (Value, Value) throws -> Value
) rethrows
}
It's worth maintaining this kind of thing, for usage at call site, but the language should make it so it doesn't need to be written. I think what we need is the ability to manually dictate how to handle labels, via a decoration on either each tuple label, or just the tuple itself, if that's decided to be enough control.
For input, labels need to match, or not. I don't know if the matching is ever actually important, but Slava is suggesting to throw out the conversion, which I think is much more useful. So we at least need an option.
For output, there's currently no way to preserve labels after de/restructuring. You either throw them out or assign new ones. I haven't followed the variadic generics discussion and don't know if a solution for this is built in there. It also may be out of scope for this discussion but it seems to me that addressing input and output should be handled for the same release.
For example, make it possible to write a function
with the argument: (a: true, b: 1)
that returns: (a: true, b: 2)
whose body is only: (tuple.0, tuple.1 + 1)
and never makes internal reference to the labels a or b in code
(and of course isn't assigned back to a tuple variable with those labels).