Tuple labels in generic constraints

Hi,

I'm having an issue with a generic function which accepts a collection of tuples:

struct Foo {
  public mutating func append<CollectionType, StringType>(
    contentsOf keyValuePairs: CollectionType
  ) where CollectionType: Collection, CollectionType.Element == (StringType, StringType), StringType: StringProtocol {
    // ...
  }
}

This function only appears to work if the tuples are unlabelled. If, for instance, I wanted to append the contents of a Dictionary (whose Element is the labelled tuple (key: Key, value: Value)), it won't compile:

var x = Foo()
x.append(contentsOf: ["bar": "bar", "baz": "baz"])
// Error: instance method 'append(contentsOf:)' requires the types '(key: String, value: String)' and '(StringType, StringType)' be equivalent

However, if I take the tuple out of the generic constraints, things appear to work as expected:

struct Foo {
  public mutating func append_single<StringType>(
    _ keyValuePair: (StringType, StringType)
  ) where StringType: StringProtocol {
  }
}

var x = Foo()
x.append_single(["bar": "bar", "baz": "baz"].first!) // Works.

Unfortunately, this workaround isn't really acceptable - I really do need to be able to append an entire Collection of tuples, meaning I need the tuple to be expressed in the generic constraints.

The only solution I can think of is just to duplicate the function - one version with tuple labels, and another without, which lazy-map to call a common implementation. It's fine (as long as I can predict the precise labels users will use), but it's a bit clunky.

What I'd like to know is: is this a bug (i.e. unintentional)? And if not, are there language enhancements that could be made so that the presence of a tuple label does not prohibit users from calling the function which does not use labels?

4 Likes

In the mean time, you can do

x.append(contentsOf: ["bar": "bar", "baz": "baz"].lazy.map { $0 })

I believe this is because the labeled -> unlabeled transformation is modeled as a conversion/subtype relationship, which doesn't satisfy same-type requirements. IMO the missing feature here is something like generalized supertype constraints which would let you express CollectionType.Element: (StringType, StringType), i.e., "CollectionType.Element is any tuple of two elements of StringType".

2 Likes

Oh, that's interesting. I don't think it would need full generalised supertype constraints (as nice as that would be), but if our existing supertype constraints could be extended to tuples, that would be awesome and fix this issue.

I could see there even being an argument that all tuple constraints should be supertype rather than same-type constraints, but that would probably be ABI breaking.