SE-0361: Extensions on bound generic types

I like the 'Detailed Design' section, it does a really good job going through the edge cases. I wonder if we could say a few words about generic typealiases in this context as well. Eg,

typealias G<T> = [T?]

extension G<Int> {}

There's also a restriction here that might be worth writing down here even if it's not directly related. You can only extend a generic typealias if the root of the underlying type is a concrete nominal, or some other alias that recursively obeys this restriction. So this is not allowed:

typealias G<T: P> = T.A  // dependent underlying type
protocol P { associatedtype A }

struct S: P {...}

extension G<S> {...}

Extension binding would have to do associated type inference to resolve G<S> to S.A, but we have to do extension binding before performing any other name lookups.

9 Likes

+1
No brainer, easier to read, easier for newcomers to understand.

Plays well with other improvements to generics such as some/any. Good job!

+1 Easy vote.

Beautiful and simple. Absolutely yes.

The overall proposal looks great to me. Thanks to the author!

In the proposal the author mentioned why we don't allow extension Pair<Int, _>:

The types specified in the type argument list must be concrete types. For example, you cannot extend a generic type with placeholders as type arguments:

extension Pair<Int, _> {} // error: Cannot extend a type that contains placeholders

Rationale: When _ is used as a type placeholder, it directs the compiler to infer the type at the position of the underscore. Using _ in a bound generic extension would introduce a subtly different meaning of _, which is to leave the type at that position unconstrained, so Pair<Int, _> would mean different things in different contexts.

While I understand _ was introduced in type declarations as type placeholder, using extension Pair<Int, _> to extend the Pair type seems natural to me (the extension only cares about the first generic parameter).

In Swift the underscore keyword already have different semantics, like let _ = .... I am not sure if we really want to allow only one meaning for _ in type declarations. If there is no technical difficulty I would prefer extension Pair<Int, _> over extension Pair where T == Int.

3 Likes

Yes, I agree. Sure, the meaning here is slightly different, but I don't think it will lead to any confusion. Besides, the _ here would be unconstrained in the extension code, but whenever that code is used in practice, the type will be constrained.

1 Like

Another option to avoid the potential semantics ambiguity in extension Pair<Int, _> is to use extension Pair<Int, *>, as the asterisk character has already been used for matching-any semantics like @available(macOS 11.0, *).

8 Likes

The problem is that this may introduce an ambiguity for _ in type position. Having a way to write an unconstrained type parameter would also be useful for opaque result types and constrained existential types, e.g. some Publisher<_, Never> and any Publisher<_, Never>, but then if you were to use that on a local variable, whether _ means "unconstrained" or "inferred" is ambiguous.

I agree that it would be very useful to have a way to write this, but I'm not sure _ is the answer, and I think we should have that discussion more holistically to find a solution that works for bound generic extensions as well as primary associated type constraints.

7 Likes
  • What is your evaluation of the proposal?

Big +1 – It feels like it should have been there already.

Except I'm not sure about sugared spelling. Especially this possibility mentioned in the future directions:

extension <Wrapped> [Wrapped?] { ... }

Crypticity of the continuous mix of angles and sugars...
But this is one possibility of the future, and, for newcomers sugars may be welcomed. So I'm not strongly against with.

  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?

Yes!

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

A reading.

1 Like

A big no no for me :pensive::pensive::pensive:

Care to elaborate on why?

1 Like

Strong +1. This was a missing feature for too long and I'm very glad that we finally get it now.

Definitely. It has always been weird that you couldn't extend Array<String> but had to write Array where Element == String instead, especially because it was already possible to extend a typealias StringArray = Array<String>. This therefore removes a big inconsistency in the language and I'm all for it.

For me it definitely does. You can already write Array<String> or even [String] everywhere in Swift, so why would this be the only exception?

Since extensions are quite a unique feature of Swift, I haven't used anything like it in another language before.

I followed the pitch thread closely and fully read the proposal.

It isn't the swift way of coding. We should just leave it this way.

I agree that the basic idea here is squarely in the "obviously it should work this way" category. Being able to use angle brackets rather than a where clause for extension declarations will be a very nice enhancement.

I am not in favor of allowing the sugared spellings like extension [Foo] {. I understand the argument about simplicity. But I actually think that, because the sugared types are so special -- there are only three, and there is no way for a user to add, reuse, or otherwise customize them -- it is simpler and more uniform to disallow them. I think permitting them will make for awkward scanning and reading. The sugared spellings are used widely, but they are not used in this manner, i.e., for top-level declarations. They're used for type annotations and expressions; it's not even possible to use them on the left hand side of a typealias.

Overall I look forward to this being added.

3 Likes

Apparently, your view on what is the swift way of coding is not shared by many people, because a clear majority in this thread seems to be sympathetic with this proposal.

So maybe you need to give some reasons for why it isn't the swift way of coding in your opinion. Otherwise it's quite hard to imagine – at least for me – why one would think that extension Array where Element == String {} is somehow more swifty than either extension [String] {} (I can at least somewhat understand that some people don't like this syntax, even though I'm in favour of it), or extension Array<String> {}, when it literally makes the language more consistent by allowing the same syntax everywhere. In other words, this proposal allows the use of the more swifty syntax (that I consider [String] to be over Array where Element == String) in more places.

2 Likes

They’re allowed to have their opinion, and they don’t have to elaborate on it if they don’t want to.

The Language Workgroup isn’t likely to give it much weight as it is, but nonetheless, they’re entitled to have their say.

3 Likes

Yeah, at which point did I say or imply that they aren't allowed to have their opinion?
I just said that it would be nice if they elaborated on it so that this opinion can be understood by other people, because I really want to understand it.

This is a very sensible proposal. It reduces a point of friction and makes an already implemented feature of extensions more accessible.

It's more like syntactic-splinter-removal rather than syntactic sugar. :+1:t3:

This was my thought too. While I think the rationale in the proposal to require the use of a where clause in such a case would be fine, it does seem like * would be a logical and intuitive option here for an unconstrained type, and feels consistent with the rest of Swift.

This review is now over, and SE-0361 has been accepted.

3 Likes