[Pitch] Extensions on bound generic types

  • Does this example not missing the where T: Hashable constraint?
  • And it shouldn't be <T...> as we have no such generic syntax yet.
extension <T: Hashable> (T...): Hashable { ... }

Overall a very welcoming addition to the language. +1 for me as well.

Indeed, if you only want tuples to conform to Hashable when each element is Hashable, it's missing a T: Hashable :slightly_smiling_face:

There is also no such extension <T> syntax yet. That example in the Future Directions section is meant to show how parameterized extensions could potentially compose with variadic generics.

5 Likes

I'm very much in favor of this pitch, it's a natural extension to the language (no pun intended lol) and it removes a weird sharp-corner I've encountered a number of times.

I was wondering about this example in the future directions section:

Wouldn't this actually be equivalent to the following? Or am I misunderstanding?

extension Array where Element: Equatable { ... }

It seems like this would/should be allowed already because of SE-0328. But, I'm having trouble seeing how all the various proposals actually interact with each other.

1 Like

The main proposal seems very straight forward, makes you wonder why extension [String] wasn't always possible. But could you elaborate on what parametrized extensions would do?

This for example seems to be the same as just omitting the <Wrapped>? I mean if you extend Array, you get access to Element, so presumably if you extend Array<Optional> you would get access to Element.Wrapped? OR perhaps you wouldn't be able to write extension Array<Optional> at all without this addition?

You literally can't express this in the language otherwise. You might be able to spell it as Array<Optional<some Any>>, but that would already be sugar for the parameterized extension, so you might as well support the long form.

Let's just see what it would look like without parameterized extensions or the proposed sugar:

extension Array where Element == Optional<???> {

What would you even put there?

Why not simply extension Array<Optional>? We already allow extending generics without specifying what the generic parameter should be, so I would expect this to work in nested positions too.

If we want to constrain the nested type, then I would instinctively assume that this would work:

extension Array<Optional> where Element.Wrapped: P

I mean I think it's strange that Element just pops up from nowhere when you extend an Array, it would have made more sense to me with a syntax like

extension Array<T> where T: Codable

but given that it's currently written

extension Array where Element: Codable

we might as well support nesting.

Overall +1

I do foresee some confusion around this for newcomers in the future:

extension Array<Element> {} // error: Cannot find type 'Element' in scope

It would be a very natural thing to try, but I acknowledge the ambiguity if it was allowed.

Maybe we should just... allow this?

In the case where there's no externally-defined Element type, (I think) it would be unambiguous. In the case where there is an externally-defined Element type, we could force users to disambiguate by either dropping the <Element> (if they meant to extend the unbound Array type) or specifying Array<MyModule.Element> (if they really did mean to extend the bound type).

Not sure what the source compatibility impact of this would look like—it wouldn't be great to start warning on a bunch of code that used to be fine. But if constructions like this end up rare, maybe it would be worth it for the sake of consistency.

OK so maybe allowing until disambiguation is required would work;

struct Element { } 

extension Array<Element> {} // error: Cannot disambiguate Element from type parameter Element and struct Element.

And then fix with either;

extension Array { }  // extend with type parameter

extension Array where Element == Element { } // extend with bound type struct Element

I'm still unsure if its clearer or more complicated.

I think the second fix would have to be

extension Array<MyModule.Element> {

since it wouldn’t make much sense for the Element on each side of the == to mean something different, IMO.

1 Like

I think that in our attempt to avoid confusion we would cause more. By allowing Array<Element> we would have to introduce complicated rules with regard to disambiguation. This could also make it harder for the compiler to resolve Element if there are already errors in the program, making the development experience worse. Further, as you mention:

So if this weren’t allowed, the rule that Array<T> == Array where Element == T would essentially be broken (or at least obscured).

But this is certainly a good opportunity to make sure diagnostics guide users in the right direction.

3 Likes

I think the proposal as-written already breaks the simplest version of this model, because the following compiles just fine today (albeit with a 'redundant constraint' warning):

struct Element {}
extension Array where Element == Element { // extension Array
    var y: Int { 1 }
}

Array<Int>().y // 1

Further, regardless of whether extension Array<Element> is allowed in the unambiguous case (i.e., when there's not an actual Element type in scope), I think we should offer a warning under this proposal when the user attempts to write it:

struct Element {}
extension Array<Element> {} // warning

something like:

warning: extending the bound generic type 'Array<MyModule.Element>'
fix-it: if this is intended, fully qualify the type to silence this warning (add 'MyModule.')
fix-it: if you meant to extend the unbound 'Array', remove '<Element>'

That is to say, I don't think that disallowing extension Array<Element> rescues us from the "complicated rules" required for disambiguation (which I don't actually think are that complicated :slightly_smiling_face:).

Given that a large part of the motivation of this proposal is parity with new generics features, and we don't allow extension Collection<Element>, we probably don't need to resolve the question of extension Array<Element> as part of this proposal—we'd probably be better off addressing for both protocols and generic types down the line.

3 Likes

But what would be the point of writing extension Array<Element> if Element is the generic type from Array. Isn't it exactly the same as writing extension Array?

2 Likes

I don’t think it’s clearly the right choice, but there are some things I like about it:

  • mirrors the declaration syntax struct Array<Element>
  • perhaps makes clearer that uses of Element inside the extension are referring to the generic parameter and not some external Element type
  • people may be drawn to it more since this proposal allows the extension Array<GenericArg> syntax

That said, there are definitely drawbacks to allowing it (it would plausibly be more confusing if bound and unbound extensions use the same syntax differentiated based only on name), so it’s not an obvious home run.

Anyway, I’ve convinced myself it’s orthogonal enough to the pitch at hand that I probably won’t discuss it more in this thread, in the interest of staying on-topic.

1 Like

Good point. Perhaps I’d be more appropriate to say that the rule is that

extension Array<T> {}

// Is equivalent to:

extension Array where Self.Element == T {}

Agreed.

Heh, only in the specific case of Array since Array defines a typealias Element = Element. But in the fully general case, generic parameters aren't members, so this, for instance, fails:

extension Array where Self.Element == Int {}

struct S<T> {}
extension S where Self.T == Int {} // error: 'T' is not a member type of generic struct 'S<T>'
1 Like

:disappointed:

5 Likes

Big +1.

A syntax like extension Array<String> feels like it always has been there.
Good for beginners. Also in general. It brings consistency and brevity.

But not sure about extension for sugars: [String]. Feels doing something clearly wrong.
However at the same time I'm not particularly against, since it might be just I'm not used to it.

Also feeling a connection with this pitch: [Pitch] Use “some” to express “some specialization of a generic type” - #10 by GreatApe


I remember earlier days: I was a beginner but began to understand some part of it and tried to create extensions for Array.

extension Array<String> { ... // error

Then I was like
:thinking:

This is now under review! Please take any further discussion over there.

1 Like